Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion l10n/.schema-l10n-baseline.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"uncovered": 1634
"uncovered": 1632
}
13 changes: 12 additions & 1 deletion l10n/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -1738,7 +1738,18 @@ OC.L10N.register(
"No meetings": "No meetings",
"Previous month": "Previous month",
"Table": "Table",
"Today": "Today"
"Today": "Today",
"Source app": "Source app",
"External reference": "External reference",
"consultationType=tender: the winning party recorded when the award decision is made (status=awarded). Decidiq owns publish/manage/award only; authoring is procest and bidding is pipelinq.": "consultationType=tender: the winning party recorded when the award decision is made (status=awarded). Decidiq owns publish/manage/award only; authoring is procest and bidding is pipelinq.",
"schema.org annotation matching the ORI type (ChooseAction / Event / CreativeWork).": "schema.org annotation matching the ORI type (ChooseAction / Event / CreativeWork).",
"consultationType=tender: public tender reference/notice number.": "consultationType=tender: public tender reference/notice number.",
"Association or VvE": "Association or VvE",
"Company board": "Company board",
"Every schema, generated values": "Every schema, generated values",
"Municipality": "Municipality",
"None, I will set this up myself": "None, I will set this up myself",
"Works council": "Works council"
},
"nplurals=2; plural=(n != 1);"
)
13 changes: 12 additions & 1 deletion l10n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1737,7 +1737,18 @@
"No meetings": "No meetings",
"Previous month": "Previous month",
"Table": "Table",
"Today": "Today"
"Today": "Today",
"Source app": "Source app",
"External reference": "External reference",
"consultationType=tender: the winning party recorded when the award decision is made (status=awarded). Decidiq owns publish/manage/award only; authoring is procest and bidding is pipelinq.": "consultationType=tender: the winning party recorded when the award decision is made (status=awarded). Decidiq owns publish/manage/award only; authoring is procest and bidding is pipelinq.",
"schema.org annotation matching the ORI type (ChooseAction / Event / CreativeWork).": "schema.org annotation matching the ORI type (ChooseAction / Event / CreativeWork).",
"consultationType=tender: public tender reference/notice number.": "consultationType=tender: public tender reference/notice number.",
"Association or VvE": "Association or VvE",
"Company board": "Company board",
"Every schema, generated values": "Every schema, generated values",
"Municipality": "Municipality",
"None, I will set this up myself": "None, I will set this up myself",
"Works council": "Works council"
},
"pluralForm": "nplurals=2; plural=(n != 1);"
}
13 changes: 12 additions & 1 deletion l10n/nl.js
Original file line number Diff line number Diff line change
Expand Up @@ -2038,10 +2038,21 @@ OC.L10N.register(
"{n} open action items": "{n} openstaande actiepunten",
"{responded} of {invited} responded": "{responded} van {invited} gereageerd",
"{states} states, {transitions} transitions": "{states} statussen, {transitions} overgangen",
"Source app": "Bronapplicatie",
"External reference": "Externe referentie",
"Which kind of organisation is this for?": "Voor wat voor organisatie is dit?",
"Pick the organisation this app runs for and you get a worked example of it: bodies, meetings, decisions and templates that fit. Installing the app plants nothing on its own, so this is the only step that adds data. Choose \"None\" on a production install.": "Kies de organisatie waarvoor deze app draait en je krijgt een uitgewerkt voorbeeld: organen, vergaderingen, besluiten en sjablonen die daarbij passen. De app zet zelf niets klaar, dus dit is de enige stap die gegevens toevoegt. Kies \"Geen\" op een productieomgeving.",
"Load the example data": "Laad de voorbeeldgegevens",
"Loads the set you picked. The data is obviously sample data, it is safe to run more than once, and you can delete it afterwards.": "Laadt de set die je koos. De gegevens zijn herkenbaar voorbeeldgegevens, je kunt dit meer dan een keer uitvoeren en je kunt ze daarna verwijderen."
"Loads the set you picked. The data is obviously sample data, it is safe to run more than once, and you can delete it afterwards.": "Laadt de set die je koos. De gegevens zijn herkenbaar voorbeeldgegevens, je kunt dit meer dan een keer uitvoeren en je kunt ze daarna verwijderen.",
"consultationType=tender: the winning party recorded when the award decision is made (status=awarded). Decidiq owns publish/manage/award only; authoring is procest and bidding is pipelinq.": "consultationType=tender: de winnende partij, vastgelegd zodra het gunningsbesluit is genomen (status=awarded). Decidiq beheert alleen publiceren, beheren en gunnen; het opstellen gebeurt in procest en het inschrijven in pipelinq.",
"schema.org annotation matching the ORI type (ChooseAction / Event / CreativeWork).": "schema.org-annotatie die hoort bij het ORI-type (ChooseAction / Event / CreativeWork).",
"consultationType=tender: public tender reference/notice number.": "consultationType=tender: het openbare aanbestedings- of publicatienummer.",
"Association or VvE": "Vereniging of VvE",
"Company board": "Raad van bestuur",
"Every schema, generated values": "Elk schema, gegenereerde waarden",
"Municipality": "Gemeente",
"None, I will set this up myself": "Geen, ik richt dit zelf in",
"Works council": "Ondernemingsraad"
},
"nplurals=2; plural=(n != 1);"
)
13 changes: 12 additions & 1 deletion l10n/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -2037,10 +2037,21 @@
"{n} open action items": "{n} openstaande actiepunten",
"{responded} of {invited} responded": "{responded} van {invited} gereageerd",
"{states} states, {transitions} transitions": "{states} statussen, {transitions} overgangen",
"Source app": "Bronapplicatie",
"External reference": "Externe referentie",
"Which kind of organisation is this for?": "Voor wat voor organisatie is dit?",
"Pick the organisation this app runs for and you get a worked example of it: bodies, meetings, decisions and templates that fit. Installing the app plants nothing on its own, so this is the only step that adds data. Choose \"None\" on a production install.": "Kies de organisatie waarvoor deze app draait en je krijgt een uitgewerkt voorbeeld: organen, vergaderingen, besluiten en sjablonen die daarbij passen. De app zet zelf niets klaar, dus dit is de enige stap die gegevens toevoegt. Kies \"Geen\" op een productieomgeving.",
"Load the example data": "Laad de voorbeeldgegevens",
"Loads the set you picked. The data is obviously sample data, it is safe to run more than once, and you can delete it afterwards.": "Laadt de set die je koos. De gegevens zijn herkenbaar voorbeeldgegevens, je kunt dit meer dan een keer uitvoeren en je kunt ze daarna verwijderen."
"Loads the set you picked. The data is obviously sample data, it is safe to run more than once, and you can delete it afterwards.": "Laadt de set die je koos. De gegevens zijn herkenbaar voorbeeldgegevens, je kunt dit meer dan een keer uitvoeren en je kunt ze daarna verwijderen.",
"consultationType=tender: the winning party recorded when the award decision is made (status=awarded). Decidiq owns publish/manage/award only; authoring is procest and bidding is pipelinq.": "consultationType=tender: de winnende partij, vastgelegd zodra het gunningsbesluit is genomen (status=awarded). Decidiq beheert alleen publiceren, beheren en gunnen; het opstellen gebeurt in procest en het inschrijven in pipelinq.",
"schema.org annotation matching the ORI type (ChooseAction / Event / CreativeWork).": "schema.org-annotatie die hoort bij het ORI-type (ChooseAction / Event / CreativeWork).",
"consultationType=tender: public tender reference/notice number.": "consultationType=tender: het openbare aanbestedings- of publicatienummer.",
"Association or VvE": "Vereniging of VvE",
"Company board": "Raad van bestuur",
"Every schema, generated values": "Elk schema, gegenereerde waarden",
"Municipality": "Gemeente",
"None, I will set this up myself": "Geen, ik richt dit zelf in",
"Works council": "Ondernemingsraad"
},
"plurals": null,
"pluralForm": "nplurals=2; plural=(n != 1);"
Expand Down
5 changes: 4 additions & 1 deletion lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
namespace OCA\Decidiq\AppInfo;

use OCA\Decidiq\AppInfo\Registrar\AppHostRegistrar;
use OCA\Decidiq\AppInfo\Registrar\CrossAppEventRegistrar;
use OCA\Decidiq\AppInfo\Registrar\DomainServiceRegistrar;
use OCA\Decidiq\AppInfo\Registrar\IntegrationLeafRegistrar;
use OCA\Decidiq\AppInfo\Registrar\ObjectListenerRegistrar;
Expand All @@ -46,7 +47,8 @@
* than accumulating here:
*
* - {@see AppHostRegistrar} AppHost boilerplate adoption (ADR-040 / ADR-022).
* - {@see DomainServiceRegistrar} decision events, MCP tools, eIDAS, translation.
* - {@see CrossAppEventRegistrar} inbound cross-app command listeners (ADR-041).
* - {@see DomainServiceRegistrar} MCP tools, eIDAS, translation.
* - {@see PlatformIntegrationRegistrar} search, object-write guards, dashboard widget.
* - {@see IntegrationLeafRegistrar} server-side half of the OR integration leaves (ADR-066).
* - {@see ObjectListenerRegistrar} boot()-time filtered object-lifecycle subscriptions.
Expand Down Expand Up @@ -140,6 +142,7 @@ public function register(IRegistrationContext $context): void {
// them.
// @spec openspec/changes/migrate-comments-to-talk-leaf/tasks.md#task-2.1.
// @spec openspec/specs/user-settings/spec.md
(new CrossAppEventRegistrar())->register(context: $context);
(new DomainServiceRegistrar())->register(context: $context);

// Board portal Phase 2 services (audit log, conflict of interest,
Expand Down
81 changes: 81 additions & 0 deletions lib/AppInfo/Registrar/CrossAppEventRegistrar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

/**
* Decidiq cross-app event registrar.
*
* Wires the typed events other fleet apps dispatch to command Decidiq, and the
* listeners that answer them. Split out of DomainServiceRegistrar because the
* set grew: what was one event/listener pair for decisions is now two, and each
* pair costs that class two more imports — enough to push it past the coupling
* threshold, which is how this extraction was found rather than guessed.
*
* Per ADR-041 a cross-app COMMAND travels as a typed event. Decidiq's REST
* controllers are the door for EXTERNAL callers; they refuse a request with no
* signed-in user, which is exactly the state an in-process migration runs in.
*
* @category AppInfo
* @package OCA\Decidiq\AppInfo\Registrar
*
* @author Conduction Development Team <info@conduction.nl>
* @copyright 2026 Conduction B.V.
* @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* @link https://conduction.nl
*
* @spec openspec/changes/governance-body-events/specs/governance-body-events/spec.md
*
* SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>
* SPDX-License-Identifier: EUPL-1.2
*/

declare(strict_types=1);

namespace OCA\Decidiq\AppInfo\Registrar;

use OCA\Decidiq\Event\DecisionRequestedEvent;
use OCA\Decidiq\Event\GovernanceBodyRequestedEvent;
use OCA\Decidiq\Listener\DecisionRequestedListener;
use OCA\Decidiq\Listener\GovernanceBodyRequestedListener;
use OCP\AppFramework\Bootstrap\IRegistrationContext;

/**
* Registers every inbound cross-app command listener.
*
* @spec openspec/changes/governance-body-events/specs/governance-body-events/spec.md
*/
class CrossAppEventRegistrar {

/**
* The command contract, as event class => listener class.
*
* A map rather than four call sites, so adding a command is one line and
* the whole inbound surface reads as one list.
*
* @var array<class-string, class-string>
*/
private const COMMANDS = [
// Raise a governance Decision for a consumer's object, and conclude it
// back through DecisionConcludedEvent.
DecisionRequestedEvent::class => DecisionRequestedListener::class,

// Hold a governance body — a committee, a board — with its roster.
GovernanceBodyRequestedEvent::class => GovernanceBodyRequestedListener::class,
];

/**
* Register every command listener.
*
* @param IRegistrationContext $context The registration context.
*
* @return void
*
* @spec openspec/changes/governance-body-events/specs/governance-body-events/spec.md
*/
public function register(IRegistrationContext $context): void {
foreach (self::COMMANDS as $event => $listener) {
$context->registerEventListener(event: $event, listener: $listener);
}

}//end register()

}//end class
25 changes: 0 additions & 25 deletions lib/AppInfo/Registrar/DomainServiceRegistrar.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@

namespace OCA\Decidiq\AppInfo\Registrar;

use OCA\Decidiq\Event\DecisionRequestedEvent;
use OCA\Decidiq\Listener\DecisionRequestedListener;
use OCA\Decidiq\Mcp\DecidiqToolProvider;
use OCA\Decidiq\Service\EIDASSignatureService;
use OCA\Decidiq\Service\IEIDASSignatureService;
Expand Down Expand Up @@ -73,35 +71,12 @@ class DomainServiceRegistrar {
* @spec openspec/specs/mcp-tools/spec.md
*/
public function register(IRegistrationContext $context): void {
$this->registerDecisionEvents(context: $context);
$this->registerMcpToolProvider(context: $context);
$this->registerEidasBindings(context: $context);
$this->registerTranslationAdapter(context: $context);

}//end register()

/**
* The event contract for delegated decisions.
*
* Consumer apps dispatch DecisionRequestedEvent (handled here ->
* createDecision) and listen for DecisionConcludedEvent (emitted from
* DecisionLifecycleService). In-process replacement for the broken
* IntegrationService::getLeaf path.
*
* @param IRegistrationContext $context The registration context
*
* @return void
*
* @spec openspec/changes/decidesk-decision-events/specs/decidesk-decision-events/spec.md
*/
private function registerDecisionEvents(IRegistrationContext $context): void {
$context->registerEventListener(
event: DecisionRequestedEvent::class,
listener: DecisionRequestedListener::class
);

}//end registerDecisionEvents()

/**
* Register DecidiqToolProvider as the MCP tool provider for the AI Chat Companion.
*
Expand Down
121 changes: 121 additions & 0 deletions lib/Event/GovernanceBodyCreatedEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

/**
* Decidiq GovernanceBodyCreatedEvent
*
* Emitted after Decidiq has raised or matched a GovernanceBody in response to a
* GovernanceBodyRequestedEvent. Carries the request's correlationId home so the
* producing app can attach the resulting id to whatever it dispatched for,
* without holding a reference to the request instance.
*
* @category Event
* @package OCA\Decidiq\Event
*
* @author Conduction Development Team <info@conduction.nl>
* @copyright 2026 Conduction B.V.
* @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* @link https://conduction.nl
*
* @spec openspec/changes/governance-body-events/specs/governance-body-events/spec.md
*
* SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>
* SPDX-License-Identifier: EUPL-1.2
*/

declare(strict_types=1);

namespace OCA\Decidiq\Event;

use OCP\EventDispatcher\Event;

/**
* Conclusion event for a cross-app governance-body command.
*
* @spec openspec/changes/governance-body-events/specs/governance-body-events/spec.md
*/
class GovernanceBodyCreatedEvent extends Event {

/**
* Construct the conclusion event.
*
* @param string $governanceBodyId The id Decidiq created or matched
* @param string $sourceApp App id of the producer the command came from
* @param string $externalReference The producer's own id for the originating record
* @param boolean $created True when this command minted the body, false when one matched
* @param string $correlationId Correlation id echoed from the request
*
* @spec openspec/changes/governance-body-events/specs/governance-body-events/spec.md
*/
public function __construct(
private readonly string $governanceBodyId,
private readonly string $sourceApp,
private readonly string $externalReference,
private readonly bool $created,
private readonly string $correlationId = '',
) {
parent::__construct();

}//end __construct()

/**
* Get the resolved GovernanceBody id.
*
* @return string The id
*
* @spec openspec/changes/governance-body-events/specs/governance-body-events/spec.md
*/
public function getGovernanceBodyId(): string {
return $this->governanceBodyId;

}//end getGovernanceBodyId()

/**
* Get the producing app id.
*
* @return string The app id
*
* @spec openspec/changes/governance-body-events/specs/governance-body-events/spec.md
*/
public function getSourceApp(): string {
return $this->sourceApp;

}//end getSourceApp()

/**
* Get the producer's own reference.
*
* @return string The external reference
*
* @spec openspec/changes/governance-body-events/specs/governance-body-events/spec.md
*/
public function getExternalReference(): string {
return $this->externalReference;

}//end getExternalReference()

/**
* Whether the body was newly created rather than matched.
*
* @return boolean The created flag
*
* @spec openspec/changes/governance-body-events/specs/governance-body-events/spec.md
*/
public function isCreated(): bool {
return $this->created;

}//end isCreated()

/**
* Get the correlation id echoed from the request.
*
* @return string The correlation id
*
* @spec openspec/changes/governance-body-events/specs/governance-body-events/spec.md
*/
public function getCorrelationId(): string {
return $this->correlationId;

}//end getCorrelationId()

}//end class
Loading
Loading