Skip to content

fix(mdm): import the trust-configuration register, and document MDM#2018

Open
MWest2020 wants to merge 1275 commits into
developmentfrom
fix/import-trust-configuration-register
Open

fix(mdm): import the trust-configuration register, and document MDM#2018
MWest2020 wants to merge 1275 commits into
developmentfrom
fix/import-trust-configuration-register

Conversation

@MWest2020

Copy link
Copy Markdown
Member

Heropend na de migratie van Codeberg naar GitHub (2026-07-14).
Origineel, met review-historie: https://codeberg.org/Conduction/openregister/pulls/356

rubenvdlinde and others added 30 commits July 6, 2026 13:40
…on via aiCompanion prop' (#294) from feat/ai-companion-opt-in into development
…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.
…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-.
Part of nextcloud-vue#102 (browser-side brokered dataSources).
…owser broker endpoint' (#304) from feat/session-broker-endpoint into development
rubenvdlinde and others added 23 commits July 10, 2026 17:13
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
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.
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.
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.

4 participants