Skip to content

fix(import): reconcile magic tables for every imported schema — closes #2082#2086

Merged
rubenvdlinde merged 1 commit into
developmentfrom
fix/magic-table-column-sync
Jul 24, 2026
Merged

fix(import): reconcile magic tables for every imported schema — closes #2082#2086
rubenvdlinde merged 1 commit into
developmentfrom
fix/magic-table-column-sync

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Closes #2082.

Root cause

ensureTableForRegisterSchema() was reachable from only two import paths:

  • autoCreateRegisterIfApplication()newly auto-created registers only
  • importSeedData()schemas that ship seed objects only

An app shipping a register.d fragment that adds a property to an existing schema in an existing register hits neither, so the physical column is never created — not even by a forced re-import. It only appears if something later happens to write an object of that type, because the save path does call ensureTableForRegisterSchema().

The sync logic itself was fine — it simply was never invoked from import.

Why it's worse than a silent no-op

When the schema's own RBAC read rule matches on the new property, the generated SQL references a non-existent column, so every read throws:

GET /apps/openregister/api/objects/11/beoordeeling → 500
SQLSTATE[42703]: Undefined column: column t.status does not exist

Fail-closed still holds (nothing leaks), but the public read path is a hard error.

Observed live (softwarecatalog, 2026-07-24)

beoordeeling gained status (public read rule matched on it → anonymous 500) and gebruik gained the TIME fields. 11 columns across two schemas were missing, so three already-merged features could not persist their data at all.

Fix

After import, reconcile every imported schema against every register that holds it (a schema can belong to several, each with its own table). Failures are logged and never fatal — an otherwise-successful import must not be lost because one table could not be reconciled.

Verification

  • Live, end-to-end: with the patch, a single import created all 11 previously-missing columns — gebruik.time_classification/time_rationale/time_review_date, module.bbn_level/dpia_status/dpia_date/dpia_volgende_beoordeling/dpia_document_ref/verwerkingsregister_ref/eol_product_slug — and the anonymous read that had been throwing returned 200. Baseline captured before/after; environment restored afterwards.
  • Unit: 5 new tests (reconciles every schema × every owning register; one failing register doesn't abort the rest; no-MagicMapper no-op; non-Schema/id-less entries skipped; register-resolution failure non-fatal). 5/5 pass.

Note: tests/Unit/AppHost/BootstrapFactoryChainTest.php breaks full-suite discovery on phpunit-unit.xml — confirmed pre-existing (reproduced with my changes stashed), so the new test was run directly.

Built in a worktree off development to avoid disturbing other in-flight openregister work.

🤖 Generated with Claude Code

A configuration import updated the schema definition but never reconciled
the physical magic table, so a property added to an EXISTING schema in an
EXISTING register never got a column.

ensureTableForRegisterSchema() was only reached from two places:
  - autoCreateRegisterIfApplication() — new, auto-created registers only
  - importSeedData()                  — schemas that ship seed objects only

An app shipping a register.d fragment that adds a property to an existing
schema hit neither, so the column simply never appeared — not even after a
FORCED re-import. The column only materialised if something later happened
to write an object of that type, because the save path does call
ensureTableForRegisterSchema().

That is worse than a silent no-op when the schema's own RBAC read rule
matches on the new property: the generated SQL references a column that
does not exist, so EVERY read — including the anonymous public path —
returns a raw SQL error.

Observed live on softwarecatalog 2026-07-24: 'beoordeeling' gained
'status' (public read rule matched on it → HTTP 500 on every anonymous
read) and 'gebruik' gained the TIME fields; 11 columns across two schemas
were missing, so three shipped features could not persist their data.

Fix: after import, reconcile every imported schema against every register
that holds it. Failures are logged, never fatal — an otherwise-successful
import must not be lost because one table could not be reconciled.

Live-verified: a single import created all 11 previously-missing columns
(gebruik time_classification/time_rationale/time_review_date; module
bbn_level/dpia_*/eol_product_slug/verwerkingsregister_ref) and the
anonymous read that had been throwing returned 200.
@rubenvdlinde
rubenvdlinde merged commit cab88db into development Jul 24, 2026
15 of 18 checks passed
@rubenvdlinde
rubenvdlinde deleted the fix/magic-table-column-sync branch July 24, 2026 18:16
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openregister @ 8a6713e

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

Quality workflow — 2026-07-24 18:20 UTC

Download the full PDF report from the workflow artifacts.

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