Skip to content

fix(federation): delete the orphaned publish wrapper and test the seam that ships (gate-57) - #447

Merged
rubenvdlinde merged 1 commit into
developmentfrom
fix/gate57-orphaned-federation-publish-wrapper
Aug 7, 2026
Merged

fix(federation): delete the orphaned publish wrapper and test the seam that ships (gate-57)#447
rubenvdlinde merged 1 commit into
developmentfrom
fix/gate57-orphaned-federation-publish-wrapper

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

What gate 57 found, and why the obvious reading was wrong

Gate 57 (orphaned-write-capability) named FederationService::publishEntryForFederation() — zero non-test production callers.

The obvious reading of a dead write capability is "the feature is missing". Here it was the opposite. PublicationService::publish() is live, through PublicationController::publish(), routed as publication#publish (PUT /api/publication/{objectType}/{uuid}/publish, appinfo/routes.php:195). Catalog entries can be published; the wrapper was a second seam that nothing used.

It was also the weaker of the two. Compared with the routed path it was missing:

  • the per-object authorizeEntry() IDOR guard (ADR-005) — admin, or an aanbod-beheerder whose organisation owns the entry; peer-sourced mirrors refused;
  • the optional ISO-8601 $when moment — its signature was (string $objectType, string $uuid), so a scheduled publication could not be expressed through it at all.

Wiring a route to it would therefore have duplicated a live capability and widened the auth surface. It is deleted, with its private getPublicationService() helper and the now-unused import.

The tests moved to where the code ships

The two testPublishEntryForFederation* tests were the only callers of the method anywhere in the repo — they constructed the service and invoked something no production path could reach. Deleting them exposed that there was no PublicationControllerTest at all: the live publish path and its IDOR guard were untested.

tests/Unit/Controller/PublicationControllerTest.php now covers:

  • an admin publishes and $when is forwarded to the service;
  • a non-owning aanbod-beheerder gets 403 and publish() is never reached;
  • a peer-sourced (federated mirror) entry is refused even for an admin.

Verified both directions

Run in a disposable nextcloud:32-apache container (PHP 8.3) with the app bind-mounted at custom_apps/softwarecatalog, so the deployed code is the code under test. No shared dev instance was touched.

result
gate 57 before 1 finding — publishEntryForFederation
gate 57 after 0 findings
new tests, guard intact 3/3 pass (17/17 across both changed test files)
new tests, guard removed + $when dropped (mutant) 3/3 FAIL

The mutant run is the positive control: all three assertions are about the guard and the forwarded argument, not about the mock shape.

php -l clean. phpcs: 0 errors on both changed lib/ files (the two remaining warnings are the pre-existing missing-@spec-on-class notices both classes already carried).

Spec

openspec/specs/federated-catalog-sync/spec.md named the deleted wrapper as the publish seam. It now names PublicationController::publish() and states explicitly that federation does not own a second publish entry point. The requirement's actual invariant — visibility enforced by the OpenRegister public RBAC read gate {group:public, match:{publicatiedatum:{$lte:$now}}} — is unchanged and still satisfied.

No waiver, no @orphaned-write-capability exclude, no baseline entry: the gate goes green because the dead code is gone.

🤖 Generated with Claude Code

…m that ships

Gate 57 (orphaned-write-capability) named
`FederationService::publishEntryForFederation()` — zero non-test production
callers. Tracing the callers showed the capability is NOT missing, which is
the opposite of what a dead write-capability usually means.

`PublicationService::publish()` is live through
`PublicationController::publish()`, routed as `publication#publish`
(`PUT /api/publication/{objectType}/{uuid}/publish`, appinfo/routes.php:195).
The federation wrapper was a pass-through to that same service call, minus
two things the routed path has:

  * the per-object `authorizeEntry()` IDOR guard (ADR-005), and
  * the optional ISO-8601 `$when` moment — its signature was
    `(string $objectType, string $uuid)`, so a scheduled publication was
    unreachable through it.

So it was not an unwired capability waiting for a caller; it was a second,
weaker publish seam that nothing used. Wiring a route to it would have
duplicated a live capability and widened the auth surface. It is deleted,
along with its private `getPublicationService()` helper and the now-unused
import.

The two unit tests that named it were its ONLY callers anywhere in the repo.
They are replaced by tests on the seam that actually ships — there was no
`PublicationControllerTest` at all, so the live publish path and its IDOR
guard were untested:

  * an admin publishes and `$when` is FORWARDED to the service;
  * a non-owning aanbod-beheerder gets 403 and `publish()` is never reached;
  * a peer-sourced (federated mirror) entry is refused even for an admin.

Verified both directions, in a disposable nextcloud:32-apache container
(PHP 8.3, the app bind-mounted at custom_apps/ so the deployed code is the
code under test):

  | | result |
  |---|---|
  | gate 57 before | 1 finding — publishEntryForFederation |
  | gate 57 after | 0 findings |
  | new tests, guard intact | 3/3 pass (17/17 across both files) |
  | new tests, guard removed + `$when` dropped (mutant) | 3/3 FAIL |

The mutant run is the positive control: all three assertions are about the
guard and the forwarded argument, not about the mock.

openspec/specs/federated-catalog-sync/spec.md named the deleted wrapper as
the publish seam. It now names `PublicationController::publish()` and states
that federation does not own a second publish entry point — the requirement's
real invariant (visibility enforced by the OpenRegister public RBAC read gate
on `publicatiedatum`) is unchanged and still satisfied.

phpcs: 0 errors on both changed lib/ files.
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/softwarecatalog @ 0a5e560

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue-demi
test-l10n
composer ✅ 121/121
npm ✅ 853/853
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-08-07 06:33 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit a7d2490 into development Aug 7, 2026
29 checks passed
@rubenvdlinde
rubenvdlinde deleted the fix/gate57-orphaned-federation-publish-wrapper branch August 7, 2026 08:14
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