fix(gates): gate-57 1→0 (retire groupfolder storage, closes #87), gate-16 84→0, CVE-2026-65954 - #100
Merged
Merged
Conversation
…5954 phpcsstandards/phpcsutils < 1.2.3 carries CVE-2026-65954 (arbitrary code execution, GHSA-r6hr-vr92-vv28, affected >=1.0.0-alpha1,<1.2.3). The advisory was published today, so composer audit turns red on a lock file that has not changed. The Security (composer) job in run 31516037194 passed at 17:25Z only because it ran before the advisory landed. Negative control before the bump, on this tree: composer audit --locked -> Found 1 security vulnerability advisory affecting 1 package phpcsstandards/phpcsutils / CVE-2026-65954 / exit 1 After 'composer update phpcsstandards/phpcsutils --no-install --no-scripts' (1.2.2 => 1.2.3, a lock-only change, 0 installs 0 removals): composer audit --locked -> No security vulnerability advisories found / exit 0 The bump is exercised rather than merely locked: phpcs runs green against the new library on PHP 8.4 -- 0 ERRORS AND 468 WARNINGS IN 211 FILES, exit 0 so no sniff regressed on the upgrade.
…oses #87, gate-57 1 -> 0) gate-57 (orphaned-write-capability) reported exactly one finding on this repo: lib/Service/DashboardService.php:3189 method=writeDashboardContent rule=orphaned-write-capability class=DashboardService It is a true positive and the real scope is larger than one method. Task 5 of the original change -- the wiring that would have routed dashboard get/create/update/delete through the storage factory -- was ticked in tasks.md but never performed. Everything around it landed, which is why the capability looked complete from every angle except the one that mattered. Measured on this tree, every absence claim taken with a positive control (the same search resolves callers for getUserDashboards and ->findByUuid): DashboardService::writeDashboardContent() 0 callers DashboardService::readDashboardContent() 0 callers DashboardService::deleteDashboardContent() 0 callers DashboardContentStorageFactory::getStorage() 0 (only the three above) DashboardApiController::storageUnavailableResponse() 0 callers DashboardMapper::findAll() 1 -- the migration command So nothing ever populated oc_launchpad_dashboards.content, Dashboard:: jsonSerialize() emitted a content key that was always null, launchpad. content_storage was a no-op setting, and launchpad:storage:migrate-to-groupfolder migrated nothing. Retired rather than completed, per Ruben's decision on #87: finishing it needs an ADR-level ruling on whether the content blob or the WidgetPlacement rows are the source of truth for a dashboard's layout. The spec asserts the blob, the code has used the rows since long before this capability existed, and wiring the blob in as a second source of truth invites divergence. Nobody uses it, so retiring costs nothing and removes that risk permanently. Removed: lib/Service/DashboardContentStorage/ (interface, both backends, three exceptions), DashboardContentStorageFactory, the three DashboardService facade methods and the factory constructor parameter, storageUnavailableResponse(), DashboardMapper::findAll(), both CLI commands and their <command> registrations, the $content/$locale properties with decodeContent() and their jsonSerialize() keys, and the five PHPUnit classes covering the removed tree. Version002009Date20260811000000 drops the content and locale columns. That loses no data: the only writer, DbContentStorage::write(), was reachable only through the factory, which was reachable only through the three orphaned facade methods, so the columns were NULL on every row of every install. The step is idempotent (hasColumn-guarded). Version002001Date20260603000000 and DashboardTableBuilder::addContentStorageColumns() are deliberately KEPT -- a shipped migration is part of the version ledger and deleting it would desynchronise instances that have already run it. Deliberately NOT removed, and tracked as Task 8 on #87: launchpad.content_storage, AdminSettingKey::CONTENT_STORAGE, SetupWizardService::{get,set}ContentStorage / hasGroupfolderApp, AdminController::setWizardStorage and setup-wizard step 2. Those belong to the setup-wizard capability, which WRITES the setting; this capability was the READER. The setting was already inert before this change, so removing the reader does not make it worse, and retiring the wizard step edits a different spec and deserves its own review. The spec is marked withdrawn rather than deleted, which also retires its 16 @e2e exclude markers -- their stated reasons cited DbContentStorageTest.php, a file this change deletes, so they would otherwise have become sixteen exemptions resting on a class that no longer exists. Verification, all on this tree: gate-57 over lib/Service/*.php 1 -> 0 planted true positive (postGate57Probe, a tracked write method with no caller) 0 -> 1, naming exactly the plant plant removed 1 -> 0 PHPUnit 1570 tests, 4032 assertions, 0 failures, 3 pre-existing skips phpcs 0 errors, 468 warnings, 211 files -- identical to the pre-change baseline check_spec_anchors.py over lib/ 0 dangling anchors gate-19 unchanged at 77 (base origin/beta) NOTE for anyone reproducing gate-57: its checker takes a FILE LIST, not a directory. `check_orphaned_write_capability.py .` prints nothing and exits 0 whatever the tree contains, which reads exactly like a clean repo.
Every other active change dir under openspec/changes/ carries one (schema/created/status); the retirement change was missing it.
…(84 -> 0) gate-16 (spec-coverage) reported 84 changed methods without an @SPEC tag against base origin/beta. All 84 were in src/; zero were PHP -- the 273 lib/**/*.php files in the same diff are fully annotated already, established with a positive control (WeatherService.php carries 2 tags, HealthPingController 3) so that "all in one layer" is a measurement rather than a failed lookup. Closed with 84 real @SPEC anchors and exactly 2 reason-bearing exclusions. The anchors point at the specs the methods actually implement -- principally clock-weather-widgets, iframe-embed-widget, live-data-tile-widget, service-health-ping and tile-quick-search. Every one resolves: the canonical check_spec_anchors.py reports 0 dangling anchors across all of src/. That matters because a dangling anchor fails SILENTLY in this gate family, so an unresolvable tag would have made the gate green while asserting nothing. The two exclusions, both verified against the source rather than argued from the scenario text: ClockWidget.vue::pointOnCircle -- pure polar-to-cartesian trigonometry. The body reads neither this.now nor this.content, so it encodes no clock, timezone, format or accessibility behaviour. Verified its only two callers are tickCoords and handCoords, and both carry the REQ-CLOCK-003 tag. useTileClickTracking.js::__resetTileClickTrackingForTest -- test-only harness hook. Verified by grep that its only importer anywhere in the repo is src/composables/__tests__/useTileClickTracking.spec.js; it implements no product behaviour, only clearing cachedEnabled / inflightConfigPromise so each test starts from a cold cache. Both reasons name a concrete artifact (a caller, an importing test file) rather than a state of the world, which is the shape that survives re-checking. This diff is comment-only and provably so: 0 added and 0 removed lines fall outside a docblock. No runtime behaviour can change. Verification: gate-16 (base origin/beta) 84 -> 0 check_spec_anchors.py 0 dangling anchors across src/ tools/check-spec-annotations.php unchanged at 12 pre-existing PHP findings (TileClickMapper, WidgetPlacement, TileClick, ImportLaunchpadRegister, Version002007) -- none introduced here, none in a file this PR touches phpcs 0 errors, 468 warnings, 211 files (baseline) No gate was weakened: no baseline, no allowlist widening, no skipped test.
…-003
Landed a moment after the main annotation commit. gate-16 was already 0
without it, so this adds no gate movement -- but the method builds the
accessible label that spells out the temperature unit, which is exactly what
REQ-WEATHER-003 ("Locale-aware units and language, WCAG AA") governs, so the
tag belongs there.
Verified: check_spec_anchors.py reports 0 dangling anchors for the file, and
REQ-WEATHER-003 exists at openspec/specs/clock-weather-widgets/spec.md:119.
rubenvdlinde
requested review from
Rem-Dam,
SudoThijn,
WilcoLouwerse,
bbrands02,
remko48 and
rjzondervan
as code owners
August 11, 2026 21:04
The note claimed a 'REQ-GRID-010 "Placement key regeneration"' requirement. REQ-GRID-010 is titled **Grid Styling**; the stale scenario merely sits inside it, at spec.md:405. Reworded to say exactly that, to name the line, and to record that getPlacementKey() -- the method the scenario requires -- no longer exists. A wrong diagnosis written into a code comment is worse than no comment: the next reader inherits it as established fact and nobody re-measures it. Filed the substance as #101 rather than leaving it only in a docblock.
Contributor
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| composer | ✅ | ✅ 102/102 | |||
| npm | ✅ | ✅ 548/548 | |||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-11 21:06 UTC
Download the full PDF report from the workflow artifacts.
Contributor
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| composer | ✅ | ✅ 102/102 | |||
| npm | ✅ | ✅ 548/548 | |||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-11 21:24 UTC
Download the full PDF report from the workflow artifacts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Takes launchpad's Hydra Gates job from three red gates to one. gate-19 is untouched and is the only remaining failure.
Starting point, measured not assumed
From the failing
development → betarun31516037194ate24334b6(job log pulled viagh api …/jobs/93866053044/logs, 101,235 bytes), reproduced locally byte-for-byte on a detached worktree with a freshly cloned canonicalConductionNL/.github@57bcb2bandHYDRA_GATE_BASE_REF=origin/beta:Hydra Gates is the only red job — every other job is green and
Quality Reportis the pure aggregator.development → betadiff scope CI actually gates on. Do not read the two as a regression or a win.What this PR does
1. CVE-2026-65954 (
phpcsstandards/phpcsutils1.2.2 → 1.2.3)Lock-only.
composer audit --lockedexit 1 → exit 0. Exercised by running phpcs against the new library on PHP 8.4 (0 ERRORS AND 468 WARNINGS IN 211 FILES, exit 0), not merely locked. TheSecurity (composer)job passed at 17:25Z only because it ran before the advisory landed; the next run would have gone red with no diff to explain it.2. gate-57 → 0 — retire the inert
groupfolder-storage-backendcapability (closes #87)The finding was
DashboardService::writeDashboardContent, and the real scope was the whole capability. Task 5 of the original change — the wiring — was ticked intasks.mdbut never performed. Every absence claim below was taken with a positive control (the same search resolves callers forgetUserDashboardsand->findByUuid):writeDashboardContent/readDashboardContent/deleteDashboardContentDashboardContentStorageFactory::getStorage()DashboardApiController::storageUnavailableResponse()DashboardMapper::findAll()Version002009Date20260811000000drops thecontentandlocalecolumns. That loses no data: the only writer was reachable solely through the orphaned facade methods, so the columns were NULL on every row of every install. The step ishasColumn-guarded and idempotent.Version002001Date20260603000000andDashboardTableBuilder::addContentStorageColumns()are deliberately kept — a shipped migration is part of the version ledger.Deliberately NOT removed, tracked as Task 8 on #87:
launchpad.content_storage,AdminSettingKey::CONTENT_STORAGE,SetupWizardService::{get,set}ContentStorage/hasGroupfolderApp,AdminController::setWizardStorageand setup-wizard step 2. Those belong to thesetup-wizardcapability, which writes the setting; this one was the reader. The setting was already inert, so removing the reader does not make it worse, and retiring the wizard step edits a different spec and deserves its own review.The spec is marked withdrawn rather than deleted. That also retires its 16
@e2e excludemarkers, whose reasons citedDbContentStorageTest.php— a file this PR deletes, which would otherwise have left sixteen exemptions resting on a class that no longer exists.3. gate-16 → 0 — annotate the 84 changed frontend methods
All 84 were in
src/; zero were PHP. That the PHP side is genuinely clean rather than unscanned was established with a positive control (273lib/**/*.phpfiles in the same diff;WeatherService.phpcarries 2 tags,HealthPingController.php3).Closed with 84 real
@specanchors and exactly 2 reason-bearing exclusions, both verified against the source:ClockWidget.vue::pointOnCircle— pure polar-to-cartesian trigonometry; body reads neitherthis.nownorthis.content. Verified its only two callers aretickCoords/handCoords, both carrying REQ-CLOCK-003.useTileClickTracking.js::__resetTileClickTrackingForTest— test-only harness hook; verified its only importer in the repo isuseTileClickTracking.spec.js.Both reasons name a concrete artifact rather than a state of the world. This diff is comment-only and provably so: 0 added and 0 removed lines fall outside a docblock, so no runtime behaviour can change.
Verification
Every number below was produced by the canonical checker on this branch.
No gate was weakened: no baseline, no allowlist widening, no
@e2e excludeadded, no skipped or deleted test other than the five PHPUnit classes covering deleted production code.@paramtype from theDashboardServiceconstructor docblock broke PEAR's param-alignment sniff across all 22 remaining params — 44 phpcs errors into a file that had zero. Caught only by re-measuring against the pre-change baseline. It was fixed with the repo's ownphpcbf, not by adjusting the standard.Not addressed here
@e2e excludeanywhere in the six affected specs, so the 77 is honest debt rather than an artifact of.github#345.WidgetPlacement'sint+addType('integer')precedent over the identicalSMALLINTcolumn) compiles, passes, and silently disables the feature. Three PHP consumers usegetIsInclude() === trueand the editor uses!== false;1 === trueis false in PHP and0 !== falseis true in JS. The precedent is safe there because its consumers use truthiness. The honest fix is a column type change plus data migration.tools/check-spec-annotations.phpfails on 12 PHP methods (TileClickMapper,WidgetPlacement,TileClick,ImportLaunchpadRegister,Version002007). Unchanged by this PR, none in a file it touches, and not run by any CI job.