Skip to content

Repair steps that write during occ upgrade run as 'Anonymous' — 26 fixed, 18 remain, and the identity gate is fleet-wide #574

Description

@rubenvdlinde

The defect

A repair/migration step registered in appinfo/info.xml runs during occ upgrade, where there is no session. OpenRegister resolves the actor as Anonymous and refuses the write.

It fails silently. Steps report it with $output->warning(), which does not fail an upgrade — so the upgrade prints Update successful while nothing was written.

The gate is fleet-wide, not app-specific

Probed five registers with a deliberately invalid payload (so nothing could be created either way) and read which gate answered:

decidiq              governance-body       IDENTITY-GATE   User 'Anonymous' does not have permission to 'create'
shillinq             Taakveld              IDENTITY-GATE   …
trust-configuration  TrustConfiguration    IDENTITY-GATE   …
stackiq              Contract              IDENTITY-GATE   …
dossiq               bacAdviceRequest      IDENTITY-GATE   …

Every one refuses before validation is reached. So any step that creates objects on upgrade without an identity is broken.

Two measured cases

app before after
decidiq MigrateLegacyTemplatesToDecisionTemplate 12 failures, 14 templates unmigrated 0
shillinq InitializeSettings 8 failures across BBV/Barcode/Inventory/Mandaat/FixedAsset seeds 0

⚠️ Three findings that make a mechanical sweep wrong

1. _rbac: false is NOT an exemption. 30 of 45 candidates pass it, and it looks like a solution. shillinq's InitializeSettings had 6 write sites and 6 _rbac: false — apparent full coverage — and still produced 8 live failures. The refusals arrive from writes further down the call chain, inside services a seeder delegates to, where no per-call flag reaches.

2. currentUser: $adminUser IS a valid solution, and some authors already measured it. shillinq's RematerialiseConvertedCalculations documents "without this, 173 real objects failed to re-save on occ maintenance:repair". Those steps were left alone — wrapping a proven fix adds risk, not safety.

3. A step that skips is indistinguishable from one that succeeded. On a seeded instance most steps report (0 new) and never attempt the write that would fail. Absence of failures on a populated box proves nothing.

Why the wrap over the flag

_rbac: false must be remembered at every write site including ones the step doesn't make itself. One runAsSystem() scope covers the whole step, can't be half-applied, and automatically covers a write added later.

Done (26 steps)

decidiq 2 · dossiq 4 · shillinq 9 · pipelinq 2 · hermiq 12 — each with a RunsUnderSystemIdentity trait that resolves defensively and runs the work either way, so adding an identity never costs the degradation behaviour that was already there.

⚠️ Test fakes need attention both ways: a fake lacking runAsSystem let 16 decidiq tests pass over a migration that wrote nothing; a createMock() that stubs it to return null made 21 correct changes look broken in hermiq and pipelinq.

Remaining (18) — each needs reading, not grepping

decidesk   RepointConflictOfInterestBoardMember, MigrateActionItemsToDeckLeaf,
           MigrateEmailLinksToRegistry, MigrateCommentsToTalkLeaf,
           MigrateBoardProxyToProxyAuthorization
shillinq   FoldExpensesAndHoursIntoProject, PeriodCloseBackfill,
           DelegateSigningMigrationRepair
           (+ FoldIntoOrder, FoldDunningWriteoffIntoArInvoice,
              RematerialiseConvertedCalculations — already pass currentUser)
pipelinq   IngestProductVendorMaster, SeedTrustConfigurationRows
           (+ NormaliseTicketTitle — already flags its own writes)
procest    LoadDefaultZgwMappings          hrmq       MigrateHoursProcess
stackiq    BackfillContractApprovalState   openconnector  MaterializeCatalogItems

The probe is reusable: attempt a create with an invalid payload and read whether the identity gate or validation answers first. Nothing is ever created.

Measured 2026-08-24/25 against a live NC 34 instance.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions