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.
The defect
A repair/migration step registered in
appinfo/info.xmlruns duringocc upgrade, where there is no session. OpenRegister resolves the actor asAnonymousand refuses the write.It fails silently. Steps report it with
$output->warning(), which does not fail an upgrade — so the upgrade printsUpdate successfulwhile 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:
Every one refuses before validation is reached. So any step that creates objects on upgrade without an identity is broken.
Two measured cases
MigrateLegacyTemplatesToDecisionTemplateInitializeSettings1.
_rbac: falseis NOT an exemption. 30 of 45 candidates pass it, and it looks like a solution. shillinq'sInitializeSettingshad 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: $adminUserIS a valid solution, and some authors already measured it. shillinq'sRematerialiseConvertedCalculationsdocuments "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: falsemust be remembered at every write site including ones the step doesn't make itself. OnerunAsSystem()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
RunsUnderSystemIdentitytrait that resolves defensively and runs the work either way, so adding an identity never costs the degradation behaviour that was already there.runAsSystemlet 16 decidiq tests pass over a migration that wrote nothing; acreateMock()that stubs it to return null made 21 correct changes look broken in hermiq and pipelinq.Remaining (18) — each needs reading, not grepping
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.