fix(mdm): import the trust-configuration register, and document MDM#2018
Open
MWest2020 wants to merge 1275 commits into
Open
fix(mdm): import the trust-configuration register, and document MDM#2018MWest2020 wants to merge 1275 commits into
MWest2020 wants to merge 1275 commits into
Conversation
…on via aiCompanion prop' (#294) from feat/ai-companion-opt-in into development
…-registration, acting-user
…af for deployed e2e # Conflicts: # appinfo/info.xml
Two fixes that make the dsar-escalation-and-dpia declared behaviour live: - data_subject_request_register.json: declare daysRemaining / isOverdue / escalationTier as schema PROPERTIES alongside their materialised calculations. A materialised calculation output is dropped at persist time unless a backing property (→ magic-table column) exists — verified live: escalationTier stayed null until declared, then persisted 'on-track' with daysRemaining 9 on a fresh case. Without this the temporal sweep recomputes a value the write path cannot store, so the calculatedChange notification rules never see an old/new tier. - ImportHandler::importSeedData: seed objects now target the register imported IN THIS RUN (registersMap) before falling back to the configuration entity's register list. On a FIRST import the entity's register list is only persisted after importFromJson returns, so seeds hit 'register 0' and every one failed with 'Cannot insert object without register and schema context' — verified live with the DSAR policy-pack seeds (2 packs now land cleanly on first import).
…l-broker-config-head # Conflicts: # openspec/changes/credential-broker/tasks.md
…eed target register - CalculationPayloadBuilder: extract the @self/@ref/@aggregate evaluation- payload prep out of CalculationOnSaveListener into a shared service so the save-time listener and the new temporal re-evaluation sweep evaluate calculations against ONE payload shape (a drifted copy would make the sweep recompute different values than the write path persists). CalculationOnSaveListener now delegates to it — behaviour unchanged. - ImportHandler::importSeedData: target the register imported IN THIS RUN (registersMap) before falling back to the configuration entity's register list. On a FIRST import the entity's register list is only persisted after importFromJson returns, so seeds hit 'register 0' and failed with 'Cannot insert object without register and schema context' (verified live with the DSAR policy-pack seeds).
…lations live
Closes the deadline-escalation gap from pipelinq's consume-or-dsar audit
(dsar-escalation-and-dpia). The DSAR register already declares an
escalationTier calculation (tiers from the active dsarPolicyPack) and three
calculatedChange notification rules (reminder / escalation / breach) — but
calculatedChange only fires from a real object write, and nothing
re-computes a time-dependent calculation for an untouched case, so the
declared notifications were dead letters for exactly the proactive case
they exist for.
TemporalCalculationSweepService (hourly TimedJob, IAppConfig toggles per
the DsarRetentionSweepJob convention) detects schemas whose materialised
calculations reference the evaluation clock (the {"now":[]} operator or a
literal "now" argument — cached per schema), selects objects in
non-terminal lifecycle states, recomputes via CalculationEvaluator against
the shared CalculationPayloadBuilder payload, and persists ONLY changed
values through the normal ObjectService write path — which emits the
standard updated event with old+new data, so AnnotationNotificationListener
fires the declared calculatedChange rules boundary-guarded (one dispatch
per tier crossing). Generic clockwork any future now-dependent schema
inherits; write amplification bounded (a case is rewritten at most once per
tier crossing). Unit-tested against the real DSAR expression shapes.
…bility Closes the DPIA-detection gap from pipelinq's consume-or-dsar audit, generalising the retired DpiaDetectionService as OR-owned art-35 tooling (ADR-047/051 §4): - DpiaPatternDetectionService (pure, unit-testable): groups DSAR cases received inside a rolling window by configurable characteristics (default type + normalised scope), reports groups reaching the threshold. Flagged cases count toward their group but are listed separately so re-runs never re-write them (idempotency). Fail-safe: unusable config yields no groups. - DsarDpiaDetectionJob (daily TimedJob): partitions cases by jurisdiction, resolves each partition's active pack, sets dpiaRequired=true on unflagged members of triggering groups via the normal write path with a dpia.detected audit row (rule/groupKey/window/count). Never clears a manual flag (one-way ratchet). No imperative notification dispatch — the declared dpiaFlagged rule fires on the write (gate-18 posture). - PrivacyOfficerRecipientResolver: the declared expression-recipient for the deadlineBreach + dpiaFlagged rules — resolves the officer GROUP from the case's active pack (privacyOfficerGroup), fail-safe to zero recipients on no pack / placeholder / unknown group (the handler recipient still fires). - DsarPolicyPackResolver: new public activePackForCase() (system-context read) so the job + resolver share one pack-resolution path. Threshold, window, grouping, and officer group are pack data (config as data, no code change to retune). Unit-tested with grouping / threshold / normalisation / window / fail-safe / idempotency matrices.
… schema descriptor + provider catalogue' (#287) from feat/credential-broker-config-head into development
…iring Declarative half of dsar-escalation-and-dpia + the wiring that makes it live: - data_subject_request_register.json (schema v1.2.0): breachedAt write-once breach stamp (declared as a calculation evaluated after escalationTier — the sweep job carries no DSAR-specific branch, ADR-031); the dpiaFlagged notification rule (dpiaRequired false→true, pack-resolved officer recipient) shared by detection + manual flagging; a second officer recipient on deadlineBreach; and daysRemaining / isOverdue / escalationTier declared as PROPERTIES so their materialised calculation outputs persist to magic-table columns (verified live: escalationTier stayed null until declared, then persisted). - dsar_policy_pack_register.json (schema v1.1.0): privacyOfficerGroup + dpiaDetection (threshold 10 / window 30d / group by type+scope, pipelinq parity) on the pack; seedData block so the default + nl-example packs land through the ConfigurationService seedData path. - ImportDsarRegisters repair step: OR does not self-import its own lib/Settings register descriptors (ADR-037, the ImportCredentialBrokerRegister precedent) — without this the declared behaviour never reaches a live instance. Version-gated, each descriptor under its own configuration appId (importFromApp version-gates per configuration row — two descriptors under one appId silently skip the second, verified live). Registered post-migration + install. - info.xml: register TemporalCalculationSweepJob + DsarDpiaDetectionJob; bump app version.
…ion-and-dpia - tests/e2e/workflows/dsar-escalation-and-dpia.spec.ts: the two proactive journeys (reminder→breach sweep with officer notification + breached stamp; DPIA seed→flag→officer), gate-19-annotated, self-skipping without a live seeded instance. - spec deltas: reason-bearing @e2e excludes on the validator/job-internal scenarios (unchanged-value skip, terminal skip, fail-safe, write-once, idempotency, below-threshold, config-as-data, manual flagging) — PHPUnit-covered. - tasks.md: all tasks complete with the two documented deviations (live tier-crossing NC-notification delivery + full browser journeys not driven in this loop; no bespoke Newman collection — no DSAR-specific REST endpoint).
The credential-broker service (broker guards, signed app tokens, vault leaf, provider catalogue) landed on development without its unit tests. This adds the four suites (18 tests): CredentialBrokerServiceTest (4 ordered guards → 403, host-lock, secret-never-in-output), CredentialAppTokenServiceTest (HMAC sign/ verify, forge, expiry), NextcloudVaultCredentialStoreTest, ProviderCatalogueTest. All green against development's service code.
…ntial-broker service' (#300) from feat/credential-broker-tests into development
…, feature doc, changelog - openspec/specs/dsar-deadline-escalation/ + dsar-dpia-detection/: both delta specs synced 1:1 (5 requirements, 13 scenarios with @e2e refs/excludes). - Change moved to openspec/changes/archive/2026-07-06-dsar-escalation-and-dpia/. - docs/features/dsar-escalation-and-dpia.md + README entry. - CHANGELOG: Unreleased entry for both capabilities + the seed-import fix.
…d arm + auto-vectorization (#301)
…R-AD-3)
archival-transfer-hardening storage layer — the durable records retry and
proofs build on:
- edepot_transfer_register.json: the edepot-transfers system register with
the edepotTransfer transfer-list schema (status, objectReferences,
exclusions, approvalMetadata, append-only attempts[], transferResult,
packageFormat) and the write-once edepotTransferProof schema (immutable:
true; ingest reference/archivId, transport receipt, package id + format +
manifest SHA-256, per-file {name, sha256}, transport, confirmedAt).
- TransferRecordService: thin persistence adapter over ObjectService —
save/load transfer lists (keyed by their own uuid so status transitions
update one durable object) and create write-once proof records (a repeated
(transfer, object) pair returns the existing proof, never duplicates).
- ImportEdepotTransferRegister repair step imports/upgrades the register
from lib/Settings (ADR-037, its own configuration appId), registered
post-migration + install; app version bump.
…l-doriath-leaf # Conflicts: # appinfo/info.xml
…offin provider' (#302) from feat/credential-doriath-leaf into development
…AD-2) Remove the in-flow sleep() retry chain; one job run = one transport attempt per outstanding package with append-only attempts[] and IJobList re-enqueue (exponential backoff + jitter, capped). TransferController index/show/create now read persisted records and refuse non-approved lists.
Per-connection edepot_package_format (zip default | bagit); writeBagitArchive emits bagit.txt, bag-info.txt (Payload-Oxum), manifest-sha256.txt, tagmanifest-sha256.txt with data/ payload; zip layout preserved byte-for-byte.
…transfer 47 Edepot/transfer unit tests green (php:8.3-cli CI way); Newman transfer collection; gate-19-annotated Playwright workflow. Change tasks complete.
Promotes edepot-bagit-output, edepot-durable-retry, edepot-proof-of-transfer to openspec/specs/ (status: done); moves the change to archive/2026-07-06-.
# Conflicts: # appinfo/info.xml
…in searchable flag (#303)
Part of nextcloud-vue#102 (browser-side brokered dataSources).
…owser broker endpoint' (#304) from feat/session-broker-endpoint into development
FunctionSpacing: collapse double blank lines between members to one across the federation entity, mapper, share service and object-source provider, matching the standard applied to the rest of the feature.
The DBAL-virtual-registers frontend strings (source/EditSource, templates, LLM settings) were present in used code + en.json but missing from every locale, failing the l10n-parity hard gate. Backfill each required locale's .json with the English source value (non-empty; parity forbids missing/empty). Clears the carried l10n-parity debt so app-tests.yml goes green. English placeholders pending real translation via the normal l10n pipeline.
…only `::text` with dialect-aware CAST(_ AS CHAR) (WOO-520) ## Symptoom `GET /apps/opencatalogi/api/search` crashte op MariaDB backends met: ``` SQLSTATE[42000]: Syntax error near '::text AS _id, _uuid::text AS _uuid, _slug::text AS _slug, _uri::text AS _uri...' at line 1 ``` De crash zit in `MagicMapper::buildUnionSelectPart()` — dat is de per-schema UNION-arm die opgebouwd wordt zodra een consumer een multi-schema search uitvoert (`_schemas: [id1, id2]`). WOO-506's SCH-PFTS endpoint doet precies dat vanaf commit 2387a7b7 (PR #88 op opencatalogi): het roept `ObjectService::searchObjectsPaginated()` aan met beide `publication` en `document` schema-ids, waarna deze UNION-builder de crash triggert. ## Root cause De cast-syntax `col::text` is PostgreSQL-only. MariaDB/MySQL vereisen de standaard `CAST(col AS CHAR)`. Op de PROPERTY-column projection (lines 1490-1512) was al platform-aware code aanwezig (via `$isPostgres` flag). De METADATA-column projection erboven (lines 1436-1458) én de NULL-column placeholders waren dat NIET — die gebruikten onvoorwaardelijk `::text`. Hydra CI runt op PostgreSQL → geen enkele gate ving deze in. Elke local dev-env op MariaDB brak zodra WOO-506's endpoint gehit werd. ## Fix Zelfde platform-switch pattern als bij de property-column loop: ```php $colExpr = "CAST({$metaCol} AS CHAR) AS {$metaCol}"; if ($isPostgres === true) { $colExpr = "{$metaCol}::text AS {$metaCol}"; } ``` Toegepast op: - `_metadataColumns` SELECT expressies (existing + NULL placeholders) `$isPostgres` is al gedefinieerd bovenaan `buildUnionSelectPart()` (line 1422) uit `stripos($platform::class, 'PostgreSQL')` — de fix hergebruikt dat. ## Out of scope voor deze fix De volgende PG-only expressies in dezelfde method (`buildUnionSelectPart()`) zijn score-berekeningen die alleen firen bij `_search` param: - Line 1557: `{$quotedCol}::text ILIKE {$likePattern}` — `ILIKE` is ook PG-only - Line 1559: `similarity({$quotedCol}::text, {$quotedTerm})` — `similarity()` is een PG extension (pg_trgm) Deze breken een `?_search=X` query op MariaDB (niet de base list-query die WOO-520 reporte). Zijn een bredere portability-refactor waard — voorstel: losse follow-up subtask op WOO-506 die de search-scoring dialect-agnostisch maakt (bijv. via `MATCH...AGAINST` op MySQL / plaintext LIKE-fallback). ## Test-plan - [ ] `php -l lib/Db/MagicMapper.php` → clean (verified) - [ ] Op MariaDB dev-env (nextcloud-docker-dev met `database-mysql`): `GET /apps/opencatalogi/api/search` returnt HTTP 200 met `{"results":[...], "total":N}` in plaats van HTTP 500 - [ ] Op PostgreSQL (Hydra CI): unchanged behavior — `$isPostgres` branch levert nog steeds `col::text` SQL - [ ] Bestaande PHPUnit-tests voor MagicMapper blijven groen ## Follow-ups - **`_search` param op MariaDB** — nog steeds broken (ILIKE + similarity()); losse subtask onder WOO-506 - **Andere PG-only spots** (95 hits total in `lib/`) — bredere OR-portability refactor; buiten scope Closes WOO-520. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Picks up the Files widget upload UX (card chrome on detail pages, upload default-on, drag-and-drop) and the page-config modal view-mode/map config (nextcloud-vue beta.168).
….0.0-beta.168' (#339) from chore/ncvue-beta168 into development
…chival-transfer-hardening, dsar-escalation-and-dpia, semantic-object-handoff-engine); branch code superseded by squash merges
…rest of branch superseded)
… casts in UNION query
Nextcloud 34's app-management page inlines the raw app.svg and recolors it via 'fill: currentcolor' on the <svg> element. A fill carried on <path>/<style>/class beats the inherited value, so the icon rendered white-on-white and vanished from the list. Moving the fill to the <svg> element lets Nextcloud recolor the icon; visuals are unchanged everywhere the icon is shown on a dark/colored surface.
…currentcolor recoloring' (#341) from fix/app-icon-svg-level-fill into development
setSelfMetadata() now applies a client-supplied @self.geo onto the object entity via setGeo(). Unlike organisation/tmlo/folder this is plain descriptive geographical metadata that drives no security or lifecycle decision, so it is client-writable; an explicit null clears it. This is the backend for map widgets (CnObjectGeoWidget) that edit an object's location via a minimal PATCH of @self.geo only.
…e (map widget backend)' (#343) from feat/object-geo-metadata into development
`nextcloud/ocp` is a require-dev STUB package (NC API signatures for Psalm /
PHPStan). It was mapped in the RUNTIME `autoload.psr-4`:
"OCP\\": "vendor/nextcloud/ocp/OCP/",
"NCU\\": "vendor/nextcloud/ocp/NCU/"
and lib/AppInfo/Application.php requires vendor/autoload.php — so every request
that touched OpenRegister registered a psr-4 map sending `OCP\*` to the stubs.
The stubs are pinned `^31.0`. On this NC 34 instance that means core's
`OC\Settings\Manager` (which carries `#[\Override]` on `getAdminDelegatedSettings()`)
was compiled against the NC 31 `OCP\Settings\IManager`, which predates that
method. PHP 8.3 raises a COMPILE-TIME fatal:
OC\Settings\Manager::getAdminDelegatedSettings() has #[\Override] attribute,
but no matching parent method exists
That takes down the WHOLE instance, not just this app: `occ` dies, `occ app:list`
returns 0 apps, and every /apps/openregister/api/* route 404s while other apps'
endpoints 500. Observed today — pipelinq's dashboard lost all of its KPI and
chart data because the aggregation endpoints were unreachable.
Moves the stub mapping to `autoload-dev`, where static analysis still resolves it
and a `--no-dev` deploy cannot leak it into the runtime autoloader.
NOTE — this is only half the mitigation. `autoload-dev` is STILL baked into the
generated autoloader by a plain `composer install` (dev mode). In this dev
topology the app checkout IS the served app, so a routine `composer install` here
re-bricks the instance. Same landmine in hermiq and launchpad. A durable fix is
either always deploying `--no-dev`, or pinning `nextcloud/ocp` to the running NC
major so a leak is harmless.
…runtime
Moving the stub mapping to `autoload-dev` (previous commit) narrowed the failure
window but did not close it: `autoload-dev` is STILL baked into the generated
autoloader by a plain `composer install`. In this topology the app checkout IS
the served app, so a routine dev install would re-brick the instance — which is
exactly how it broke today.
The mapping turns out to be entirely redundant. Both analysers already discover
the stubs through their own mechanisms, neither of which consults Composer's
autoloader:
phpstan.neon: scanDirectories: - vendor/nextcloud/ocp
psalm.xml: <extraFiles><directory name="vendor/nextcloud/ocp" /></extraFiles>
plus an explicit <stubs> entry
Verified with the mapping removed:
- `composer dump-autoload` in DEV mode emits ZERO references to nextcloud/ocp
in autoload_psr4.php / autoload_static.php → OCP can no longer leak into the
runtime autoloader under ANY install mode.
- Psalm: 124 errors, ZERO of which mention OCP. All 96 UndefinedClass errors
are Doctrine\DBAL\* and Sabre\VObject\* (NC core 3rdparty), pre-existing and
unrelated. So OCP still resolves for static analysis.
This removes the failure mode rather than narrowing it, and needs no discipline
(`--no-dev`) and no coupling to the running NC major.
…rphans
Deleting a schema that still holds objects was broken in several ways. Diagnosed
live: a "Cow" schema with one object refused to delete (correctly, 409) but the
refusal was unreadable, there was no clean way to delete it, and the UI's
delete-then-unlink ordering corrupted the register on the way past.
## The cascade
`DELETE /api/schemas/{id}?deleteObjects=true` — audit + hard-delete every object,
drop the now-empty magic table(s), delete the schema. Mutually exclusive with
`force` (both -> 400 conflicting-delete-dispositions): force ORPHANS the objects,
deleteObjects REMOVES them; an ambiguous destructive intent is refused rather
than guessed. The no-flag path is byte-for-byte unchanged (409 + objectCount),
and `force` still behaves exactly as before for API clients.
Two phases, because DROP TABLE is DDL and implicitly commits on MySQL/MariaDB
(it cannot be rolled back): phase 1 is one transaction (audit + rows + schema);
phase 2 drops the tables post-commit, best-effort. A failed drop logs a WARNING
and returns success with `tableDropped: false` — it never fails a request whose
data work already succeeded, and never pretends to roll back what it cannot.
A schema can live in MULTIPLE registers, each with its own magic table, and the
guard counts across all of them — so the cascade iterates every one of them.
Dropping only the "current" table would leave behind orphans the guard had
already counted.
## Bugs found and fixed on the way
- `ObjectService::deleteObjectsBySchema()` was a THROWING STUB, so
`POST /api/bulk/{register}/{schema}/delete-objects` had been returning HTTP 500.
The MagicMapper method it needed was already fully implemented — it just was
never wired up. Both bulk routes work again; the misleadingly-named
`bulk#deleteSchema` (which deletes objects, not a schema) is marked @deprecated.
- `MagicMapper::dropTable()` NEVER WORKED. It called
`ConnectionAdapter::quoteIdentifier()`, which does not exist on this Nextcloud —
so it threw "Call to undefined method" on every single call. That is why deleted
schemas always left their magic table behind. Now quotes via the platform, the
pattern the rest of the codebase already uses.
- `SchemaMapper::delete()`'s "objects still attached" guard queried the RETIRED
`openregister_objects` blob table, which is always empty for magic-table
objects. It counted 0 and waved everything through — a dead guard. Re-pointed at
the magic tables (direct query: MagicStatisticsHandler injects SchemaMapper, so
injecting it back would be circular) and given an explicit `force` bypass that
only the `?force=true` disposition may pass.
The three callers behind that dead guard now genuinely refuse. Two of them —
SchemasToolProvider and SchemaTool — are LLM-invokable with no controller in
front: a model acting on an ambiguous instruction must not be able to orphan
data. TablesSchemaSyncService also refuses; it retires read-only mirrors whose
objects live in Nextcloud Tables, so the repaired guard counts 0 and retirement
proceeds unchanged — and if a magic table DOES hold rows, that is real data the
sync job has no mandate to destroy.
- `TablesSchemaSyncService::retireMissing()` unlinked a schema from its register
BEFORE deleting it, and left it unlinked when the delete threw — the exact
orphaned-schema corruption this change exists to close.
- `BulkController` passed `hardDelete` unnormalized (a query/form request delivers
the string "true") into a bool-typed parameter: a latent TypeError that would
have surfaced the moment the stub was fixed.
Live-verified: no-flag 409 unchanged; both flags 400; cascade leaves 0 rows,
0 tables, 0 schema; bulk endpoint 200; 0-object cascade fine.
Tests: 19 new (SchemaDeletionService, SchemaMapper guard, destroy safety).
PHPUnit/Psalm/PHPStan/PHPMD identical to the origin/development baseline — zero
new issues. Also fixed 57 pre-existing PHPCS errors across 8 untouched files.
…its objects, leaving no orphans
The previous commit removed the `OCP\` / `NCU\` psr-4 mapping from composer.json,
which is what let the nextcloud/ocp stubs shadow core's OCP and take the whole
instance down. But the mapping was NOT purely redundant, as I first thought: CI
runs PHPUnit standalone on php:8.3-cli with no Nextcloud, and tests/bootstrap.php
loads only vendor/autoload.php — so OCP\* had nowhere else to come from.
Move the registration to the test entry point, where it belongs: a dev-only
mapping in the dev-only bootstrap, instead of in composer.json where a plain
`composer install` bakes it into the autoloader of the SERVED app.
- CI / standalone: bootstrap registers the stubs when no live NC supplies OCP.
- Served app: composer's autoloader has no OCP mapping under ANY install mode,
so it can never shadow core.
- Static analysis: unaffected — PHPStan reads the stubs via `scanDirectories`,
Psalm via `<extraFiles>`.
Registered AFTER the Doctrine stubs: IQueryBuilder declares constants referencing
Doctrine\DBAL\ParameterType, evaluated as the file is parsed, so the placeholders
must already be in the class table.
Verified against a CI-identical run (php:8.3-cli, no Nextcloud), before vs after:
baseline (mapping in composer): 14058 tests, 115 errors, 17 failures
this change (bootstrap): 14058 tests, 115 errors, 17 failures
Byte-for-byte identical — the pre-existing failures are untouched and nothing
regressed.
…tubs at runtime — they were bricking the whole instance' (#346) from fix/ocp-stubs-not-runtime-autoload into development
OpenRegister shipped `lib/Settings/trust_configuration_register.json` but never
imported it. It was the only descriptor in lib/Settings/ without a matching
repair step — ImportCredentialBrokerRegister, ImportDsarRegister and
ImportEdepotTransferRegister all exist; the trust one was simply missed when the
schema moved out of pipelinq and into OR.
So the `trust-configuration` register did not exist on any instance, and every
consumer of it degraded:
- SurvivorshipRecomputeListener::loadTrustRows() swallows the lookup failure
and returns [] — so survivorship silently fell back to each annotation's
defaultTier, i.e. the trust tiers were never applied at all;
- a leaf app seeding its own rows (pipelinq's SeedTrustConfigurationRows)
failed loudly on every `occ upgrade`:
"Trust-configuration seed failed for account/billingAddress/kvk-api:
Did expect one result but found none"
Adds ImportTrustConfigurationRegister (mirroring the e-Depot importer) and wires
it into both <post-migration> and <install>, ahead of any consumer.
Also documents the feature, which had none: docs/Features/master-data-management.md
covers trust configuration, freshness decay, effectiveFrom versioning, the
survivorship annotation, how a value is chosen, and manual overrides — including
the point people keep tripping over, that trust is configured per ATTRIBUTE, not
per source system. Webinar tracked in #349.
Verified live: register imported, and pipelinq's three trust rows seeded
("3 seeded, 0 already present"), then idempotent on re-run ("0 seeded, 3 already
present"). The three repair warnings are gone.
MWest2020
requested review from
Rem-Dam,
SudoThijn,
WilcoLouwerse,
bbrands02,
remko48,
rjzondervan and
rubenvdlinde
as code owners
July 17, 2026 08:12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Heropend na de migratie van Codeberg naar GitHub (2026-07-14).
Origineel, met review-historie: https://codeberg.org/Conduction/openregister/pulls/356