Skip to content

ci(docs): sync documentation branch with development#1509

Merged
rubenvdlinde merged 68 commits into
documentationfrom
chore/sync-from-development
May 13, 2026
Merged

ci(docs): sync documentation branch with development#1509
rubenvdlinde merged 68 commits into
documentationfrom
chore/sync-from-development

Conversation

@rubenvdlinde
Copy link
Copy Markdown
Contributor

Brings the documentation branch up to date with development so the live docs site (which deploys from documentation) reflects the latest preset/landing/tutorial work.

rubenvdlinde and others added 30 commits April 16, 2026 13:21
- Convert Dutch hardcoded strings to English t() keys in OrganisationsIndex, UploadFiles
- Wrap bare attributes and template strings across 20+ Vue files
- Wrap showSuccess/showError notifications in settings store
- Update l10n files with new keys and Dutch translations
Standardize on #actions across all components per the updated
slot naming convention in @conduction/nextcloud-vue.
Update all translation keys to use sentence case (only first letter capitalized)
instead of title case. Keys changed:
- "Add Groups" → "Add groups"
- "Active Collections" → "Active collections"
- "API Key" → "API key"
- And 400+ similar keys across all apps

Sentence case for keys improves consistency and readability while preserving
proper English grammar in translated values.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Wrap bare strings in 20+ Vue files (modals, views, settings, workflow)
- Convert Dutch strings in OrganisationsIndex and UploadFiles to English
- Fix template attributes (label=, placeholder=, title=, aria-label=)
- Wrap showSuccess/showError notifications in t()
- Regenerate l10n files with 1,863 keys (sentence case)
The two NcTextArea placeholders in EditWebhook.vue were written as inline
template expressions containing a `\n` escape:

  :placeholder="t('openregister', 'X-Custom-Header: value\nAuthorization: Bearer token')"
  :placeholder="t('openregister', 'objectType: object\naction: created')"

Vue's template compiler inlines that expression into the generated render
function, where the `\n` is turned into a real newline character inside a
single-quoted JS string. Webpack then emits that literal newline into
openregister-main.js, producing an unterminated string constant. V8 throws
"Invalid or unexpected token" on parse and the entire app bundle never
executes, so /apps/openregister/ renders a blank page under the Nextcloud
shell with no further console info.

Move both strings to `headersPlaceholder` / `filtersPlaceholder` computed
properties (where `\n` is a normal JS escape) and bind them by name. Added
a comment on the computed block so the next person who's tempted to inline
a multi-line placeholder knows why not to.
Add bind-mount for ../decidesk so the Decidesk app is available in the
dev nextcloud container alongside the other custom_apps.
…ent class name

Previously the listener decided whether to inject its script by string-
matching "OCA\\Mail\\" against the fully qualified event class name.
That was fragile: the listener is registered against the core
BeforeTemplateRenderedEvent, and Mail's own class may not appear in the
FQCN depending on how the event is dispatched.

Instead, verify the event is a BeforeTemplateRenderedEvent, cast the
response to TemplateResponse, and check $response->getApp() === 'mail'.
This is the supported way to target rendering from a specific app and
avoids the coincidental-string-match hazard.
Adds docker/mail/seed-cases.sh which creates two caseType objects
(Omgevingsvergunning, Kapvergunning) and two case objects (ZK-2026-0142,
ZK-2026-0034) in the Procest register (id=92) via the OpenRegister
objects API.

The cases are the counterparts of the emails seeded by seed-mail.sh, so
the OpenRegister mail sidebar has real link targets to demo the
email-to-case linking flow end-to-end.

Default target is http://localhost:8080 with admin:admin; override via
positional args. Script header documents prerequisites and the follow-up
TODOs (Decidesk meetings, Pipelinq leads) that are blocked on upstream
register initialisation.
…ns' into fix/header-info-email

# Conflicts:
#	lib/Db/MagicMapper/MagicSearchHandler.php
#	lib/Listener/MailAppScriptListener.php
#	src/modals/webhook/EditWebhook.vue
Proposes a cross-repo Features & Roadmap menu item mounted in every
Conduction app's NcAppNavigationSettings slot above the gear.

- Features tab: shipped capabilities from openspec/specs/, extracted
  at build time into docs/features.json (committed, also powers
  Docusaurus public features page)
- Roadmap tab: open GitHub issues from the app's own repo, sorted by
  reactions, with pipeline-label blocklist and full markdown body
  rendering
- Suggest-feature modal launched from the route header and from any
  widget/page declaring a specRef via its NcActions menu
- Extends existing GitHubHandler with listIssues() + createIssue();
  user PAT preferred, server PAT fallback with attribution prefix
- Out of scope: ADR-019 fleet rollout, Discussions, Accept->specter wiring

Capabilities:
- github-issue-proxy
- features-roadmap-menu

Validates strict.
The body-mount fix from 04948cc was accidentally reverted during a
merge conflict resolution, causing the sidebar to disappear whenever
Mail's Vue root re-renders. Restored by mounting the Vue app via
$mount() (no el:) and appending to document.body.

Gate on the presence of #initial-state-mail-accounts so the sidebar
only injects on Mail app pages without depending on transient DOM
that Mail's Vue owns.

Added fixed-position CSS on .or-mail-sidebar (higher specificity than
NcAppSidebar's .app-sidebar default so the panel floats on the right
with rounded corners, 8px margins and the classic NC chrome.

Also wires up useAttachmentDrag composable: patches Mail's
MessageAttachment DOM elements with draggable=true and a dragstart
listener that writes attachment metadata (messageId, attachmentId,
fileName, mime, size, downloadUrl) into dataTransfer under the
application/x-nc-mail-attachment MIME type. ObjectsTab already
consumes this payload and uploads the file to the dropped object via
/filesMultipart. Upstream PR to make Mail attachments natively
draggable tracked separately.

Seed script gained an extra email with three real attachments
(2 PDFs + 1 PNG) matching ZK-2026-0142 so the drag-drop flow has demo
data out of the box.
Hydra's orchestrator requires a hydra.json per change to dispatch. Adds
the schema-v2 file pointing at issue #1328 with empty depends_on (greenfield
change with no spec dependencies).

Without this file, Hydra's supervisor silently skips the change even if
it carries the `ready-to-build` label.
Create Beta - Fix faulty constructors
Specter-generated spec directories that were sitting untracked. Each
contains the standard openspec scaffold (proposal.md, design.md, tasks.md,
specs/, hydra.json) for a future implementation cycle.

The 21 specs include:
- 18 integration-* specs (calendar, contacts, deck, email, photos, etc.)
- 3 standalone: cleanup-linked-entity-type-map, fix-date-histogram-mariadb,
  seed-related-items

Several of these will need re-splitting after market-intelligence#30
landed (MAX_FEATURES_PER_SPEC: 100→15). The companion preflight in
hydra#196 will block oversized ones at builder dispatch with a clear
"spec too large" comment.
…r CVEs

Resolves PKSA-hznc-gbby-6w16 (CVE-2026-40296) and PKSA-jtdk-dcr5-f11n
(CVE-2026-35453). Cascaded minor bumps to symfony/var-dumper,
symfony/deprecation-contracts, symfony/polyfill-mbstring (all in-range).
# Conflicts:
#	src/mail-sidebar.js
#	src/mail-sidebar/MailSidebar.vue
…ration-2026-04

# Conflicts:
#	lib/Controller/OasController.php
Blockers (F03–F06):

- F03 TransitionController/Engine — gate transition() with
  PermissionHandler::hasPermission(action='update', object) and
  availableActions() with action='read'. Adds NotAuthorizedException
  catch in the controller (403 Forbidden). Without this gate any
  authenticated user could advance any object's lifecycle.
- F04 AggregationRunner — gate run() with hasPermission(action='list')
  on the schema. Adds 403 catch in AggregationController. Documents
  the per-object ACL leak that remains for schemas with object-acl /
  conditional rules; fix tracked alongside the
  aggregations-backend-native follow-up.
- F05 AnnotationNotificationDispatcher — validate every recipient uid
  via IUserManager::userExists() (per-request cached). Covers the
  `users`, `field`, and `relation` recipient kinds so attacker-
  controlled object data cannot direct notifications at admin uids.
- F06 LifecycleGuardRegistry / AnnotationNotificationDispatcher — drop
  every `\OC::$server` reference in lib/. Inject IServerContainer
  instead. Aligns with the ADR added in this same PR
  (docs/development-notes/AUDIT_2026-05-01.md). The Talk emit path
  also prefers the injected IConfig.

Concerns (F07–F15):

- F07 NotificationHistoryController — return 401 when unauthenticated;
  force `recipient = currentUid` filter for non-admins so the endpoint
  cannot dump every user's notification history.
- F08 Dispatcher.interpolate() — htmlspecialchars-escape every
  substituted value (defence in depth before NC's own render layer).
- F09 ScopesController.index() — add `@NoAdminRequired`. Endpoint was
  silently 401-ing for the very audience it's designed to serve.
- F10 UrnController — add `@NoAdminRequired` to resolve / lookup / bulk.
- F11 RealtimeController — return 401 when no user session, instead of
  the empty-stream masquerade.
- F12 LifecycleValidationListener — document the trust contract: only
  fires on `ObjectUpdatingEvent`, so callers MUST go through
  `ObjectService::saveObject()` for lifecycle integrity.
- F13 Schema::validateConfigurationArray — declare the `x-openregister-*`
  vocabulary in `ANNOTATION_VOCABULARY` and drop unknown keys at save
  time so `x-openregister-lifecycl` (typo) is caught early instead of
  round-tripping silently.
- F14 CalculationEvaluator::formatDate — document the trust model
  (schema authors are admin-equivalent; format string is not allowlisted).
- F15 AggregationRunner — cap PHP fallback at 10 000 rows
  (PHP_FALLBACK_ROW_CAP), surface `truncated: true` in the result.
  Native Postgres path is unaffected.

F16 dompdf 3.1.5 — `composer audit` on production deps reports no
known advisories. Single instantiation site at PdfReportWriter:69
(verified via grep). isRemoteEnabled=false / isPhpEnabled=false on
that path — no other Dompdf instances added.

F01 (split into four PRs) and F02 (no CI on head SHA — push trigger
excludes `platform-integration-*`, pull_request types omit
`synchronize`) are addressed in PR comment replies, not by code.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolves conflicts in:
- lib/Service/Lifecycle/TransitionEngine.php — keep F03 RBAC gate
  (NotAuthorizedException → 403); take development's named-arg style
  for getLifecycleAnnotation() calls.
- lib/Service/Aggregation/AggregationRunner.php — keep F15
  PHP_FALLBACK_ROW_CAP + truncated flag; take development's broader
  constructor (OrganisationService, optional SearchBackendInterface);
  deduplicate the two RBAC gates added in parallel on each branch and
  upgrade development's RuntimeException-based gate to
  NotAuthorizedException for proper 403 mapping.
- lib/Service/Notification/AnnotationNotificationDispatcher.php —
  keep F05 userExists() guard on extracted relation uids; take
  development's named-arg style; merge development's @param/@return
  docblock additions with the F08 escape-rationale and F06
  IServerContainer-ADR rationale.
- lib/Service/Calculation/CalculationEvaluator.php — keep F14 trust-
  model docblock; merge development's @param/@return tags.
- lib/Controller/RealtimeController.php — keep F11 401 guards on
  events() and cursor(); take development's @param/@return docblock
  additions and the new org-scoped cursor implementation.
- lib/Controller/UrnController.php — keep F10 @NoAdminRequired on all
  three methods; merge development's @param/@return docblock additions.
- lib/Controller/NotificationHistoryController.php — accept
  development's wording in the 401 error body ("Authentication
  required"). Functional behaviour unchanged.
- lib/Controller/OasController.php — accept development's spec path
  (`retrofit-2026-05-01-oas-generation`); the directory in the merged
  tree only exists under this name.

php -l on every edited file is clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolve the requested blockers by restoring a Vue 2 single-root template, hardening sidebar mount deduplication, and implementing object-card drop uploads for mail attachments. Also make seed-cases idempotent and environment-resilient with API-based ID discovery plus overrides, and add coverage for attachment drag behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
Addresses the phpcs failures flagged on the merge commit:

- TransitionEngine.php — add docblock for $permissionHandler param
  and switch the two NotAuthorizedException throws to named-parameter
  form (`message: sprintf(...)`).
- LifecycleGuardRegistry.php — add docblock for $serverContainer and
  re-align the now-three-param block.
- AggregationRunner.php — switch the NotAuthorizedException throw
  to named-parameter form. The multi-line IF closing-paren style and
  equals-sign alignment were auto-fixed by phpcbf.
- Schema.php — blank line after the if-closing brace inside
  validateConfigurationArray (auto-fixed by phpcbf).

phpcs --standard=phpcs.xml now reports 0 errors across all four files.
The 3 remaining warnings (line-length in computeMetric's match block)
predate this PR and live in unaltered code.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Round 2 of the c34082e phpcs cleanup, plus the dompdf license override
flagged as the only remaining 🔴 in the latest CHANGES_REQUESTED review.

License gate
- .license-overrides.json — add `dompdf/dompdf` (LGPL-2.1) with the same
  rationale we use for smalot/pdfparser (LGPL family is allowlisted; the
  checker can't normalise the SPDX variant). The single instantiation
  site PdfReportWriter:69 keeps isRemoteEnabled / isPhpEnabled false.

phpcs in F03/F05/F06/F07/F11 fix-commit files (the 21 errors flagged on
535973c that the c34082e pass missed)
- NotificationHistoryController — add docblocks for $userSession +
  $groupManager (F07).
- RealtimeController — add docblock for $userSession (F11).
- TransitionController — phpcbf supplied the missing `//end try`
  end-comment (F03 long try block).
- AnnotationNotificationDispatcher — add `$serverContainer` docblock
  (kept short to stay inside the 150-char hard limit), shift the five
  existing optional-param docblocks back into alignment, switch the
  three `userExists($u)` calls to named-parameter form (`uid: $u`),
  add the missing `@return bool` tag on userExists itself, and let
  phpcbf clean up the no-blank-line-after-control violations introduced
  alongside the F08 escape changes.

phpcs in upstream-side files that the full-repo gate also fails on
- PermissionHandler — add `@param Schema|null $schema` for the new
  optional cache-break parameter on buildPermissionCacheKey; phpcbf
  supplied the missing `//end try` end-comment.
- AuditTrailController — add docblocks for $userSession + $groupManager.
- ReportsController — add docblock for $logger; phpcbf added the
  missing `//end try` end-comment.
- ActionsController — add docblocks for $userSession + $groupManager;
  capitalise an inline-comment first letter.
- RegistersController — phpcbf restored two ternary spaces (auto-fix).

`./vendor/bin/phpcs --standard=phpcs.xml` now reports 0 errors across
the full lib/ tree (warnings only, all line-length on pre-existing
match arms / long format strings — none introduced by F03–F16). `php -l`
clean on every touched file. License-checker should now stop flagging
dompdf as denied.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolutions per #1353
review thread:

Blockers
- aggregations-and-calculations/tasks.md: explicit ADR-028 gating note
  pointing at split PR #1354 — change MUST NOT merge in current bundled form.
- lifecycle-annotation spec: add auth contract for POST /transition
  (NoAdminRequired, CSRF enforced, 401/403 codes + scenarios).
- lifecycle-annotation spec: forbid duplicate (from,to) transition pairs;
  this makes ObjectTransitionedEvent.action deterministically resolvable
  on direct PATCH; new 422 code lifecycle-duplicate-from-to + scenario.
- aggregations spec: new MUST clause for organisation/register
  multi-tenancy scoping on aggregate queries (closes the gap that
  PR #1357/#1419 hit) + cross-org and cross-register scenarios.
- notifications-annotation/tasks.md: explicit prerequisite note
  (depends_on aggregations-and-calculations) — PlaceholderResolver +
  cache-invalidation event live there.
- platform-capabilities.md: mail-sidebar status corrected to
  implemented; RBAC and multi-tenancy rows now reference the active
  specs (rbac-scopes, tenant-isolation-audit/lifecycle/quotas) with
  explicit archive notes for the legacy slugs; geo-metadata-kaart
  reverted to proposed (still in changes/, not yet graduated).

Concerns
- lifecycle spec: define StoppableEvent rejection-metadata contract
  (setRejectionReason / getRejectionReason on ObjectUpdatingEvent;
  ObjectService::saveObject translates stopped+reason into HTTP 422).
  Add prerequisite task 1.2a to extend the event class if missing.
- notifications spec: normative channel block format table (per kind)
  + explicit prohibition on inline webhook URLs (SSRF); end-to-end
  annotation example.
- notifications spec: scenarios for created and updated triggers,
  including only_if_changed field-diff semantics.
- aggregations spec: computeOn vocabulary ('save' default;
  'transition:<name>' explicitly cross-spec dependent on
  lifecycle-annotation, with fail-fast 422 if used without lifecycle).
- platform-capabilities.md: mark Hydra spec linter and CI catalog
  enforcement as planned, not shipped.
- three deltas/tasks.md: explicit CHANGELOG-entry obligation tasks
  (deferred to implementing PR; spec PRs document the obligation).
- lifecycle spec: auth contract requirement now captures
  NoAdminRequired in the spec body, not only in tasks.md.

Minors
- aggregations spec: time-bucket UTC semantics + v2 deferral note.
- notifications spec: throttle window grammar pinned to
  ^([1-9][0-9]*) per (second|minute|hour|day|week)$.

Composer.lock plugin-api-version downgrade (concern #14): no spec
edit; addressed in the inline review reply (CI Composer version is
the canonical regenerator; the field is an environment artefact and
the symfony bumps in the same hunk are the substantive change).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rubenvdlinde and others added 22 commits May 11, 2026 23:43
…vice

Add a slug-aware bridge to ObjectService::searchObjects() that resolves
register and schema slugs to numeric IDs via the mappers (scoped to the
caller's organisation by the standard multi-tenancy filter) and delegates
to the numeric-ID search path.

Closes the OpenBuilt smoke-test foot-gun where callers passed slugs in
@self.register / @self.schema and got zero results back — the numeric-ID
contract on searchObjects() is now strict, so any future misuse fails
loudly at the call site rather than silently returning empty. Slug-aware
callers (controller layer, OpenCatalogi, softwarecatalog) get a
one-method-call upgrade path.

Multi-tenancy honours the same _multitenancy flag every other lookup
uses; a slug that exists in another organisation but not the caller's
throws DoesNotExistException via the mappers' standard organisation
filter.

Also documents the numeric-ID contract inline on searchObjects() so the
foot-gun cannot reappear silently.
countSearchObjects(['@self' => ['schema' => $id]]) only returns a real
count when BOTH register AND schema IDs are present in the @self filter;
single-axis queries silently return 0. That made the DELETE safety guard
on schemas and registers a no-op — `DELETE /schemas/{id}` and
`DELETE /registers/{id}` succeeded even when objects were attached,
because `objectCount` was always 0 and the 409 branch never fired.

Switch both destroy() controllers to ObjectEntityMapper::getStatistics()
on the single-axis path (registerId|schemaId), which is the same handler
the rest of the controller already uses for stats display (see
SchemasController.php:288, 924 and RegistersController.php:366, 399).
After the fix, deleting a schema/register with attached objects without
`?force=true` returns 409 with the orphan count, matching the
runtime-schema-api ADR.

Also apply PHPCS auto-fixes flagged on PR #1464 review:
- RegistersController.php:539,648 — missing `//end try` end-comments.
- ImportHandler.php:2419-2423 — docblock param-type spacing.

PHPCS now reports zero errors on the touched files.
…uto-Register

Adds spec-specific unit tests for the new behaviour introduced by change
openregister-runtime-schema-api:

- tests/Unit/Service/ObjectServiceSearchBySlugTest.php (3 tests)
  covers REQ 'ObjectService.searchObjectsBySlug resolves slugs at the
  slug-aware layer': resolves both slugs into the numeric @self block,
  throws DoesNotExistException identifying register-side failures,
  throws DoesNotExistException identifying schema-side failures.

- tests/Unit/Service/Configuration/ImportHandlerApplicationTypeTest.php
  (3 tests) covers data-import-export delta REQ 'importFromApp
  auto-creates a Register for application-type configurations': first
  import creates Register with derived slug/title/schemas[], re-import
  is idempotent on (slug, org) and only updates, library/untyped
  configs are skipped.

- tests/bootstrap-spec-docker.php — Docker bootstrap that resolves
  OCA\OpenRegister\* to the spec worktree instead of the main
  custom_apps mount, so spec branch tests can run against the live NC
  container without disturbing the main mount.
…sters

Adds spec-specific unit tests for the runtime-schema-api guard introduced
in feature/runtime-schema-api:

- tests/Unit/Controller/SchemasDestroySafetyTest.php (3 tests)
  covers REQ 'Runtime schema deletion is guarded by object count':
  DELETE returns 409 with schema-has-objects + objectCount when objects
  attached and force=false, DELETE with force=true succeeds + invalidates
  cache + logs WARNING with orphan count, DELETE on unused schema
  succeeds (baseline).

- tests/Unit/Controller/RegistersDestroySafetyTest.php (3 tests)
  covers REQ 'Same CRUD guarantees apply to /api/registers':
  DELETE returns 409 with register-has-objects + objectCount when
  objects attached and force=false, DELETE with force=true succeeds +
  invalidates cache + logs WARNING with orphan count, DELETE on unused
  register succeeds (baseline).
…ntegration

Adds the canonical integration test referenced by tasks.md §4.4 — a
service-level round-trip through SchemasController that proves the cache
invalidation chain holds in the same PHP worker:

- POST /api/schemas + immediate GET — new schema visible (cache invalidated)
- PUT /api/schemas/{id} + same-worker re-read — new description observed
- DELETE without ?force=true returns 409 when objects exist
- DELETE with ?force=true returns 200; cache cleared; schema gone
- ObjectService::searchObjectsBySlug resolves a fresh slug-pair in-worker

Uses real services from \OC::\$server (SchemaMapper, RegisterMapper,
MagicMapper, ObjectService, SchemaCacheHandler, FacetCacheHandler,
SchemasController) and mocks only IRequest as a thin data carrier. Whole
suite is skipped automatically when Nextcloud isn't bootstrapped so the
host \`composer test:unit\` run stays green.

phpunit.xml updated so the Integration Tests testsuite picks up both the
existing tests/integration (newman) and the new tests/Integration (php).
…DELETE safety

Adds tests/integration/openregister.postman_collection.json — a focused,
spec-scoped Newman collection that exercises the end-to-end HTTP round-trip
the unit + service-level tests can only assert in isolation:

  1. POST   /api/registers           seed register
  2. POST   /api/schemas             create schema with `x-openregister-lifecycle`
  3. GET    /api/schemas/{id}        immediate read sees the new schema (cache cleared)
  4. PUT    /api/schemas/{id}        update description; chained GET sees update
  5. PUT    /api/registers/{id}      attach schema to register
  6. POST   /api/objects/{r}/{s}     create object against the schema
  7. GET    /api/objects/{r}/{s}/{u} read object back
  8. DELETE /api/schemas/{id}        no `?force=true` → 409 (schema-has-objects)
  9. DELETE /api/schemas/{id}?force=true → 200; chained GET → 404
 10. DELETE /api/registers/{id}?force=true → 200

Also updates openspec/.../tasks.md: 4.4 + 6.4 now closed (integration test
landed); 5.4 cross-references the unit-level ImportHandler coverage and
explicitly defers full importFromApp upload coverage to a follow-up.

Run inside the container:
  docker exec -u 33 nextcloud newman run \
    /var/www/html/custom_apps/openregister/tests/integration/openregister.postman_collection.json \
    --env-var base_url=http://localhost --env-var admin_user=admin --env-var admin_password=admin
Post-rebase onto development:
- psalm: 11 UnusedBaselineEntry errors removed (entries no longer match)
- phpstan: baseline regenerated, net -50 lines (1455 errors tracked,
  matches current source after rebase reconciled both branches)
- RegistersController gained a 19th ctor arg (RegisterCacheHandler);
  update RegistersControllerTest + RegistersDestroySafetyTest to pass
  it (plus the previously-missing ContainerInterface/IGroupManager in
  the DELETE-safety test).
- SchemasController create/update/destroy now route schema-cache cleanup
  through SchemaCacheHandler::invalidate(); update SchemasControllerTest
  expectations and add getStatistics() stubs for the new DELETE-safety
  object count. Use real Schema entities so the magic getId() resolves.
- SchemasDestroySafetyTest: append the 14th ctor arg (ContainerInterface).
- ObjectServiceSearchBySlugTest: pass the dateTimeNormalizer ctor arg.
- RegistersControllerTest destroy/* tests: use real Register entities and
  stub getStatistics() so RegisterCacheHandler::invalidate(int) is fed a
  real id.
feat(openspec): runtime-schema-api — runtime CRUD on schemas + registers
Adds the spec set for extending EntityRelation with an optional bases
JSON column. The anonymise endpoint accepts bases per entity, persists
them on the matching relation row, and strips them from the payload
before forwarding to OpenAnonymiser. Backwards-compatible — callers
that omit bases see identical behaviour.

Tightly scoped: this does not retrofit the broader EntityRelation
surface (currently uncovered by an OpenSpec capability).

Refs: #1435
Pair: ConductionNL/docudesk#135 (anonymisation-grondslagen-summary +
anonymisation-grondslagen-and-prohibition-gate hard/soft dep).
Adds the spec set for extending TextExtractionService with EML support
via zbateson/mail-mime-parser. Two outputs share the parser:

- Flat plain-text extraction (headers + body + recursively-extracted
  attachment text inline) for entity detection.
- A new public parseEmlStructured() returning headers + both text/plain
  and text/html body parts + attachments-with-bytes (recursive nestedEml
  capped at depth 3) for downstream PDF assembly.

Tightly scoped: does not retrofit the broader TextExtractionService
surface (currently uncovered by an OpenSpec capability).

Refs: #1438
Pair: ConductionNL/docudesk#135 (eml-pdf-assembly hard dep,
anonymise-output-as-pdf-by-default soft dep).
fix(mail-sidebar): restore body-mount + attachment drag-drop + header email typo
…s-adr024-027

feat(openspec): platform capabilities catalog + three declarative-annotation deltas
Blockers:
- B1: add audit-trail Requirement (ADR-022 / Woo compliance). Every
  set/update of bases produces an audit entry with previousBases,
  newBases, actor UID (per ADR-005, not display name), timestamp, and
  row identifier. Reads do not produce audit entries.
- B2: add authorization-inheritance Requirement (ADR-005 / ADR-023).
  Bases writes inherit the anonymise endpoint's existing per-object
  authorization; no extra check is added in this change, and the
  absence is documented explicitly so reviewers do not mistake it
  for oversight.

Concerns:
- C1: add endpoint-shape-validation Requirement that distinguishes
  endpoint-layer shape check (rejects non-array / non-string elements
  with 400) from mapper-layer content acceptance (any string array
  persisted verbatim). Reconciles spec ↔ tasks.md.
- C2: add retry-idempotency Requirement. Three caller intents are
  distinguished: field absent (reuse persisted), present-null (clear,
  audit-logged), present-empty-array (set to [], audit-logged).
- C3: add Notes section with Woo Art. 5 references and the six
  canonical DocuDesk base seed slugs.

Minors:
- M1: apply MUST to THEN/AND assertions throughout all Scenarios.

Tasks: update 3.1 to clarify two-layer shape vs content split; add
3.5 (audit-trail wiring), 3.6 (retry semantics), 3.7 (auth confirm);
add corresponding 4.4–4.7 test tasks.

Skipped: C4 (spec-format vs writing-specs.md) and M2 (tasks.md
spec_ref / files / acceptance_criteria) — both flag repo-wide format
drift that Wilco explicitly says does not block merge and needs
maintainer coordination.
Blocker:
- B1: tighten EmlAttachment.content contract — MUST hold decoded binary
  bytes (via $part->getContent(), not getRawContent()); consumers MUST
  NOT base64-decode again. Prevents downstream PDF/A-3 attachment
  corruption in DocuDesk's eml-pdf-assembly.

Concerns:
- C1: fix scenario notation — body accessors switched from array form
  result.body['plainText'] to value-object form result.body->plainText
  to match the typed EmlBody declaration.
- C2: parseEmlStructured changed from MAY throw to MUST throw on
  irrecoverable malformed input — so DocuDesk's exception-driven
  fallback (eml-pdf-assembly → extractEml flat text) actually fires.
- C3: add multipart/alternative scenario for the flat path — confirms
  text/plain is preferred over text/html when both are present.
- C4: add no-PII-in-logs Requirement (ADR-005) with two Scenarios —
  parser-failure log lines and sanitised exception-message logging
  MUST NOT leak addresses / names / subjects / body content /
  attachment filenames.

Minors:
- M1: clarify depth-3 recursion semantics — depth is measured as the
  number of recursive parseEmlStructured calls from the root (root =
  depth 0, limit of 3 allows parses at depths 0, 1, 2, 3).
- M2: add SHOULD Requirement + Scenario for non-UTF-8 body charset
  transcoding (mb_detect_encoding + mb_convert_encoding to UTF-8).
- M3: state the missing-filename fallback format (attachment-<n>,
  1-indexed) on EmlAttachment, with a new Scenario covering it.

Tasks: update 3.4 (filename fallback + decoded-bytes), 3.9 (MUST
throw), and add 3.10 (PII-sanitised logging) + 3.11 (explicit
multipart/alternative preference). Add tests 5.6 (multipart pref),
5.7 (filename fallback), 5.8 (content encoding), 5.9 (PII log
redaction), 5.10 (MUST throw assertion).

Skipped: C5 (spec-format vs writing-specs.md) — repo-wide drift that
Wilco explicitly says does not block merge and needs maintainer
coordination.
…grondslagen

Specs: entity-relation-grondslagen (#1435)
…nisation (#1499)

Organisation.php carried two parallel sets of the seven linked-entity
properties (mail / contacts / notes / todos / calendar / talk / deck) — a
terse "Linked X app data" set and a fully-documented "Linked X entity IDs"
set — from a merge collision between two PRs that both added the
unified-Nextcloud-entity-linking feature. PHP fatal-errors at class load
with "Cannot redeclare OCA\OpenRegister\Db\Organisation::$mail", so
`occ app:enable openregister` (and therefore every app that depends on OR)
breaks on the development branch.

Removed the terse duplicate set; kept the fully-documented one. The
constructor's addType() calls and jsonSerialize()'s `_*` keys reference the
properties by name and are unaffected. `php -l` clean; no property name now
appears twice.
# Conflicts:
#	docs/package-lock.json
#	docs/package.json
@rubenvdlinde rubenvdlinde merged commit 7a33af1 into documentation May 13, 2026
@rubenvdlinde rubenvdlinde deleted the chore/sync-from-development branch May 13, 2026 09:08
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