Skip to content

feat(publish): rescue buildScaffoldMap() — openbuild-app repos are installable standalone apps - #117

Merged
rubenvdlinde merged 2 commits into
developmentfrom
rescue/codeberg-buildscaffoldmap
Aug 4, 2026
Merged

feat(publish): rescue buildScaffoldMap() — openbuild-app repos are installable standalone apps#117
rubenvdlinde merged 2 commits into
developmentfrom
rescue/codeberg-buildscaffoldmap

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Rescued from Codeberg before the remote is removed.

ExportService::buildScaffoldMap() landed on codeberg.org/Conduction/openbuild as 4a47d797 (2026-07-26) and never reached GitHub.

Absence verified across all 230 origin refs, not just development: zero hits for buildScaffoldMap in lib/Service/ExportService.php.

Why only this one, when four commits were Codeberg-only

Three of the four did land, via merge 001ea414f "import Codeberg feature line onto GitHub". Blob-identity checked at both tips for PageDesignerHost.vue, BuilderHost.vue, AgentsController.php, GeneratedDocumentController.php, the three listeners and CHANGELOG.md — all IDENTICAL. This method is precisely what that import missed.

(The fourth Codeberg-only commit, 97c31714, untracks 559 MB of docs/build/ artefacts. GitHub still tracks 304 such files; the .gitignore rules are already identical on both sides, GitHub just never ran the git rm --cached. Trivially reproducible, no content at risk — left for a separate hygiene PR.)

The port is purely additive

lib/Resources/template/appinfo/info.xml     2  0
lib/Service/ExportService.php              43  0
tests/Unit/Service/ExportServiceTest.php   28  0

0 deletions means GitHub's copies were strict subsets, so taking the Codeberg blobs reproduces them exactly with no risk of reverting newer GitHub work. All six helpers the method calls (prepareScratchDir, copyTemplate, resolvePlaceholders, bundleDataRegisterSchemas, listFilesSorted, rrmdir) verified present on GitHub's ExportService.

One deliberate change from the original

The Codeberg docblock read:

@spec openspec/changes/federated-config-sharing/specs/federated-config-sharing/spec.md

That path does not exist on GitHub — and does not exist on the Codeberg tip either. It was a dangling anchor from the moment it was written and would fail gate-58 (spec-anchor-existence). Re-pointed at the github-app-sync spec, which is the change this method actually serves (GitHubAppSyncService folds the scaffold into the config-set repo publish) and which does exist here.

…stallable standalone apps

RESCUED FROM CODEBERG. ExportService::buildScaffoldMap() landed on
codeberg.org/Conduction/openbuild as 4a47d797 (2026-07-26) and never reached
GitHub. Codeberg is being retired, so it would have been destroyed with the
remote. Searched all 230 origin refs for 'buildScaffoldMap' in
lib/Service/ExportService.php — zero hits.

Three of the four Codeberg-only openbuild commits DID land on GitHub, via
merge 001ea41 'import Codeberg feature line onto GitHub'. Blob-identity
checked at both tips for PageDesignerHost.vue, BuilderHost.vue,
AgentsController.php, GeneratedDocumentController.php, the three listeners and
CHANGELOG.md — all IDENTICAL. This method is what that import missed.

The port is purely additive — 73 insertions, 0 deletions across the three
files — so GitHub's copies were strict subsets and taking the Codeberg blobs
reproduces them exactly. All six helpers the method calls (prepareScratchDir,
copyTemplate, resolvePlaceholders, bundleDataRegisterSchemas, listFilesSorted,
rrmdir) are verified present on GitHub's ExportService.

ONE DELIBERATE CHANGE from the Codeberg original. Its docblock read:

  @SPEC openspec/changes/federated-config-sharing/specs/federated-config-sharing/spec.md

That path does not exist on GitHub — and does not exist on the CODEBERG tip
either. It was a dangling anchor from the moment it was written, and would
fail gate-58 (spec-anchor-existence). Re-pointed at the github-app-sync spec,
which is the change this method actually serves (GitHubAppSyncService folds
the scaffold into the config-set repo publish) and which does exist here.
@rubenvdlinde

Copy link
Copy Markdown
Contributor Author

Blocked on a design decision I should not make blind

quality / PHP Quality (phpmd) fails, and this PR caused it:

lib/Service/ExportService.php:67  TooManyPublicMethods
  The class ExportService has 11 public methods.
  Consider refactoring ExportService to keep number of public methods under 10.

phpmd is green on development (runs 30883768795, 30880716742), so this is not pre-existing red — adding buildScaffoldMap() takes the class from 10 to 11. I am not merging over a failure my own change caused.

What the finding is actually pointing at

Only two of the 13 public methods have a production caller:

generateAppZip    lib/BackgroundJob/RunExportJob.php
scratchTreeDir    lib/BackgroundJob/RunExportJob.php

The other nine — bundleDataRegisterSchemas, packageZip, listFilesSorted, resolvePlaceholders, isBinary, copyTemplate, prepareScratchDir, getOrCreateAppDataDir, rrmdir — are public only so the tests can call them (tests/Integration/ExporterEndToEndTest.php, tests/Unit/Service/ExportServiceTest.php). So the threshold was already being held up by test-only visibility; this PR is the straw, not the cause.

Two ways forward, both for the service's owner to pick

  1. Narrow the surface. Make the nine test-only methods private/protected and drive them through generateAppZip() / reflection in the tests. Correct, but it rewrites two test files and is a design call on someone else's service.
  2. Baseline it. Add the finding to phpmd.baseline.xml. Cheap, but it permanently silences a class-level metric on a class that has just been shown to have a real surface problem — the exact shape of a gate that stops being able to fail.

I have deliberately done neither. The rescued code itself is verified good — purely additive (73 insertions, 0 deletions), all six helpers present, php -l clean, and the @spec anchor repaired (the Codeberg original pointed at a path that never existed on either forge).

⚠️ Do not delete the codeberg remote on this repo until this PR mergesbuildScaffoldMap() exists nowhere else. It was verified absent across all 230 origin refs.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openbuild @ 45b2151

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
composer ✅ 101/101
npm ✅ 654/654
PHPUnit
Newman ⏭️
Playwright ⏭️

Quality workflow — 2026-08-04 07:55 UTC

Download the full PDF report from the workflow artifacts.

… calls

phpmd's TooManyPublicMethods fired on this class the moment
buildScaffoldMap() was rescued, and the finding was pointing at
something real: of 12 public methods, only two — generateAppZip() and
scratchTreeDir() — had a production caller (both RunExportJob). The
other nine were public purely so the two test files could reach them.

Nine methods are now private: copyTemplate, resolvePlaceholders,
packageZip, listFilesSorted, isBinary, prepareScratchDir,
getOrCreateAppDataDir, rrmdir, bundleDataRegisterSchemas.

Both test files now drive the real entry points instead. Every
behaviour the old tests asserted directly on a step is now asserted
through its effect on what generateAppZip() / buildScaffoldMap()
produce — placeholder resolution, ASCII entry ordering, the snapshot
helper-file skip, data-register bundling, scratch-tree cleanup — plus
three assertions the old tests did not make at all. Suite: 743 -> 746
tests, 2245 -> 2342 assertions.

No baseline entry was added: phpmd is now clean on this class, and the
report set-diffs empty against development.
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openbuild @ 6546276

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
composer ✅ 101/101
npm ✅ 654/654
PHPUnit
Newman ⏭️
Playwright ⏭️

Quality workflow — 2026-08-04 08:25 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit 4ab842e into development Aug 4, 2026
33 checks passed
@rubenvdlinde

Copy link
Copy Markdown
Contributor Author

Merged after narrowing the public surface (not baselining the finding)

The TooManyPublicMethods failure this PR introduced was pointing at something real, so it was fixed rather than suppressed. No entry was added to phpmd.baseline.xml.

Caller analysis (re-measured, origin/development @ 4115de22f)

method production caller
generateAppZip() RunExportJob.php:178
scratchTreeDir() RunExportJob.php:267
buildScaffoldMap() none on this remote — see #118
copyTemplate(), resolvePlaceholders(), packageZip(), listFilesSorted(), isBinary(), prepareScratchDir(), getOrCreateAppDataDir(), rrmdir(), bundleDataRegisterSchemas() none — self-calls only; public solely so the two test files could reach them

Checked for ReflectionMethod / setAccessible / dynamic dispatch across lib/ and tests/ first: 14 sites, none of them touching ExportService. The class is not registered in AppInfo/Application.php or any event wiring; its only injection point is RunExportJob's constructor.

Those nine are now private. Public surface: constructor + 3.

The tests now drive the real entry points

Both files were rewritten. Every behaviour previously asserted by calling a step directly is now asserted through what generateAppZip() / buildScaffoldMap() actually produce:

  • placeholder resolution → appinfo/info.xml in the archive
  • listFilesSorted → archive entries equal their own ASCII-sorted copy (REQ-OBEX-008)
  • copyTemplate's skip list → .snapshot-meta.json / .path-manifest.txt absent from the archive (new — the old suite never asserted this)
  • data-register bundling (all five cases) → lib/Settings/data-registers/* entries in the archive
  • rrmdir in buildScaffoldMap's finally → no scratch tree left under openbuild-work/ (new)
  • scratchTreeDir → deterministic, job-scoped, and does not create the directory (new)
  • ExporterEndToEndTest::testReExportIsByteEquivalent now runs the whole pipeline twice instead of re-packaging one tree, so copy + resolve determinism is in scope too

Suite: 743 → 746 tests, 2245 → 2342 assertions. The e2e loop's binary-file skip is now the test's own extension list, not $service->isBinary() — asking the service what to skip made that assertion agree with itself.

Positive control

Broke two private behaviours the narrowed API still exposes — $skip = [] in copyTemplate() and sort()rsort() in listFilesSorted():

1) ExportServiceTest::testGenerateAppZipOrdersArchiveEntriesLexicographically
2) ExportServiceTest::testGenerateAppZipOmitsSnapshotHelperFiles
   Failed asserting that an array does not have the key '.snapshot-meta.json'.
FAILURES! Tests: 13, Assertions: 200, Failures: 2.

Reverted via edit (not git revert); git status --porcelain empty and git diff --exit-code 0 confirm it applied. Green again: OK (746 tests, 2342 assertions), phpmd exit 0.

phpmd set-diff, not totals

Full report compared line-for-line against development in a second worktree at 4115de22f, same vendor, same --baseline-file:

  • development: 0 findings, exit 0
  • this branch: 0 findings, exit 0
  • this branch before the narrowing: exactly 1 — ExportService.php:67 TooManyPublicMethods … has 11 public methods, exit 2 (that run is the positive control for the phpmd invocation itself)

Nothing new appeared elsewhere from making methods private.

Job sets

31 jobs in the PR's Code Quality run vs 31 on development's, identical name sets. The only conclusion differences are event-shaped: SBOM + Features Extract run on push and skip on PRs, Features Check the reverse. No gate went missing.

One thing this PR's description got wrong

GitHubAppSyncService folds the scaffold into the config-set repo publish

It does on Codeberg — GitHubAppSyncService.php:294 there. It does not here: that call site was never rescued, so buildScaffoldMap() lands dead. It cannot be a file copy either, because GitHub's GitHubAppSyncService is strictly newer (AppChannelApplier, OUTCOME_FORBIDDEN, warning-level broker logging — all absent from Codeberg's). Tracked as #118. The method stays public as a designed-but-unwired entry point, and its docblock says exactly that.

@rubenvdlinde
rubenvdlinde deleted the rescue/codeberg-buildscaffoldmap branch August 5, 2026 17:23
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.

2 participants